在 C# 里,Parallel.ForEach
是 System.Threading.Tasks
命名空间下的一个方法,它能并行处理集合中的元素。与传统的 foreach
循环不同,Parallel.ForEach
会利用多个线程同时处理集合中的元素,以此提升性能,特别是在处理大型集合或者每个元素的处理操作较为耗时的情况下。
Parallel.ForEach<TSource>(IEnumerable<TSource> source, Action<TSource> body);
source
:这是要处理的集合,它需要实现 IEnumerable
接口。body
:这是一个委托,针对集合中的每个元素都会执行此委托。using System;
using System.Collections.Generic;
using System.Threading.Tasks;
class Program
{
static void Main()
{
// 创建一个包含一些整数的列表
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// 使用 Parallel.ForEach 并行处理列表中的每个元素
Parallel.ForEach(numbers, number =>
{
// 模拟一些耗时的操作
System.Threading.Thread.Sleep(100);
Console.WriteLine($"处理数字 {number},线程 ID: {System.Threading.Thread.CurrentThread.ManagedThreadId}");
});
Console.WriteLine("所有元素处理完毕。");
}
}
numbers
。Parallel.ForEach
:对 numbers
列表里的每个元素并行执行指定的操作。Thread.Sleep(100)
模拟每个元素处理时的耗时操作。Parallel.ForEach
还有一些可选参数,可用于对并行处理进行更细致的控制:
Parallel.ForEach<TSource>(IEnumerable<TSource> source, ParallelOptions parallelOptions, Action<TSource> body);
parallelOptions
:这是一个 ParallelOptions
对象,能够用来设置最大并行度、取消标记等。ParallelOptions
)using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void Main()
{
ParallelOptions options = new ParallelOptions()
{
MaxDegreeOfParallelism = 3
};
await Parallel.ForEachAsync(Enumerable.Range(1, 10), options, async (i, c) =>
{
Console.WriteLine($"任务 {i} 开始执行...");
await Task.Delay(2000, c);
Console.WriteLine($"任务 {i} 执行完成!");
});
Console.WriteLine("所有任务执行完成!");
}
/*
任务 2 开始执行...
任务 1 开始执行...
任务 3 开始执行...
任务 1 执行完成!
任务 3 执行完成!
任务 2 执行完成!
任务 4 开始执行...
任务 5 开始执行...
任务 6 开始执行...
任务 6 执行完成!
任务 5 执行完成!
任务 4 执行完成!
任务 7 开始执行...
任务 8 开始执行...
任务 9 开始执行...
任务 9 执行完成!
任务 10 开始执行...
任务 8 执行完成!
任务 7 执行完成!
任务 10 执行完成!
任务全部完成!
*/
}
Parallel.ForEach
中访问共享资源时,要确保线程安全,可使用锁机制或者线程安全的集合。Parallel.ForEach
,在处理小型集合或者每个元素的处理操作非常快时,使用 Parallel.ForEach
可能会因为线程创建和管理的开销而导致性能下降。SemaphoreSlim
是 C# 中用于控制对有限资源访问的轻量级同步原语,它位于 System.Threading
命名空间下。下面将从基本概念、使用场景、常用方法和示例代码等方面详细介绍 SemaphoreSlim
。
SemaphoreSlim
是信号量的一种轻量级实现,信号量本质上是一个计数器,用于限制同时访问某个资源或代码段的线程数量。当一个线程想要访问受信号量保护的资源时,它需要先请求信号量。如果信号量的计数器大于 0,计数器会减 1,线程可以继续访问资源;如果计数器为 0,线程会被阻塞,直到有其他线程释放信号量。
SemaphoreSlim
可以限制同时访问这些资源的线程数量,避免资源耗尽。构造函数
SemaphoreSlim(int initialCount)
:初始化 SemaphoreSlim
实例,指定初始的信号量计数。SemaphoreSlim(int initialCount, int maxCount)
:初始化 SemaphoreSlim
实例,指定初始的信号量计数和最大的信号量计数。Wait
方法
Wait()
:请求信号量,如果信号量的计数大于 0,则计数减 1 并继续执行;如果计数为 0,则线程会被阻塞,直到有其他线程释放信号量。Wait(int millisecondsTimeout)
:请求信号量,指定等待的最大时间(以毫秒为单位)。如果在指定时间内信号量可用,则计数减 1 并继续执行;否则返回 false
。WaitAsync
方法
WaitAsync()
:异步请求信号量,返回一个 Task
,该 Task
在信号量可用时完成。WaitAsync(int millisecondsTimeout)
:异步请求信号量,指定等待的最大时间(以毫秒为单位)。返回一个 Task
,如果在指定时间内信号量可用,则计数减 1 且 Task
的结果为 true
;否则为 false
。Release
方法
Release()
:释放信号量,将信号量的计数加 1。如果有其他线程正在等待信号量,则其中一个线程会被唤醒。Release(int releaseCount)
:释放指定数量的信号量,将信号量的计数增加 releaseCount
。以下是一个简单的示例,展示了如何使用 SemaphoreSlim
来限制同时访问某个资源的线程数量:
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
// 创建一个 SemaphoreSlim 实例,初始计数为 2,最大计数为 2
private static SemaphoreSlim semaphore = new SemaphoreSlim(2, 2);
static async Task Main()
{
// 创建多个任务来模拟并发访问
Task[] tasks = new Task[5];
for (int i = 0; i < 5; i++)
{
tasks[i] = Task.Run(() => AccessResource(i));
}
// 等待所有任务完成
await Task.WhenAll(tasks);
Console.WriteLine("所有任务完成。");
}
static async Task AccessResource(int taskId)
{
Console.WriteLine($"任务 {taskId} 正在等待访问资源...");
// 请求信号量
await semaphore.WaitAsync();
try
{
Console.WriteLine($"任务 {taskId} 已获得访问资源的权限。");
// 模拟一些耗时操作
await Task.Delay(1000);
}
finally
{
// 释放信号量
semaphore.Release();
Console.WriteLine($"任务 {taskId} 已释放资源。");
}
}
}
SemaphoreSlim
实例:在 Main
方法中,创建了一个 SemaphoreSlim
实例,初始计数为 2,最大计数为 2,表示最多允许 2 个线程同时访问资源。Task.Run
方法创建 5 个任务,模拟并发访问资源的情况。AccessResource
方法中,使用 WaitAsync
方法异步请求信号量。如果信号量可用,则继续执行;否则任务会被阻塞,直到有其他线程释放信号量。try-finally
块中,使用 Release
方法释放信号量,确保无论任务是否发生异常,信号量都会被释放。try-finally
块来保证信号量的释放。WaitAsync
方法,避免阻塞线程。TPL Dataflow
(Task Parallel Library Dataflow)是 .NET 框架中的一个库,它位于 System.Threading.Tasks.Dataflow
命名空间下,为构建基于消息传递的并行和异步数据流提供了高级抽象,让开发者能够轻松构建可扩展、高效且响应式的应用程序。下面从基本概念、核心组件、使用场景、示例代码等方面详细介绍 TPL Dataflow。
TPL Dataflow
基于数据流编程模型,它将应用程序分解为一系列相互连接的处理块(Block),这些处理块通过消息传递的方式进行通信。每个处理块负责执行特定的任务,当一个处理块接收到消息时,它会对消息进行处理,并将处理结果传递给下一个处理块,以此类推,形成一个数据流。
数据块之间可以通过 LinkTo
方法进行连接,形成数据流。连接可以设置不同的传播选项,如是否在源块完成时传播完成信号等。
数据块之间通过发送和接收消息进行通信。消息可以是任何类型的对象,处理块接收到消息后会根据其功能进行相应的处理。
以下是一个简单的示例,展示了如何使用 TPL Dataflow 构建一个简单的数据流:
using System;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
class Program
{
static async Task Main()
{
// 创建一个转换块,将输入的整数转换为其平方值
var transformBlock = new TransformBlock<int, int>(num => num * num);
// 创建一个动作块,用于打印转换后的结果
var actionBlock = new ActionBlock<int>(result =>
{
Console.WriteLine($"结果: {result}");
});
// 将转换块连接到动作块
transformBlock.LinkTo(actionBlock);
// 向转换块发送一些数据
for (int i = 1; i <= 5; i++)
{
transformBlock.Post(i);
}
// 标记转换块完成
transformBlock.Complete();
// 等待动作块处理完所有消息
await actionBlock.Completion;
Console.WriteLine("所有消息处理完成。");
}
}
TransformBlock
用于将输入的整数转换为其平方值,以及一个 ActionBlock
用于打印转换后的结果。LinkTo
方法将 TransformBlock
连接到 ActionBlock
,形成一个简单的数据流。Post
方法向 TransformBlock
发送一些整数数据。Complete
方法标记 TransformBlock
完成,表示不再有新的消息发送。Completion
属性等待 ActionBlock
处理完所有消息。Completion
属性的 Exception
来捕获和处理这些异常。TaskScheduler
是 .NET 中 System.Threading.Tasks
命名空间下的一个抽象类,它在任务并行库(TPL)里扮演着核心角色,主要负责管理和调度 Task
对象的执行。下面将从基本概念、内置任务调度器、使用场景、自定义任务调度器等方面详细介绍 TaskScheduler
。
TaskScheduler
是任务调度的核心,它决定了 Task
何时、在哪个线程上执行。当你创建并启动一个 Task
时,Task
不会立刻执行,而是被提交给 TaskScheduler
进行调度。TaskScheduler
会根据自身的调度策略,将任务分配到合适的线程上执行。
.NET 提供了几个内置的任务调度器:
TaskScheduler.Default
TaskScheduler.Current
TaskScheduler.Current
可以确保子任务使用与父任务相同的调度器。TaskScheduler.FromCurrentSynchronizationContext()
TaskScheduler.FromCurrentSynchronizationContext()
可以确保任务在 UI 线程上执行,避免因跨线程访问 UI 控件而导致的异常。using System;
using System.Threading.Tasks;
class Program
{
static void Main()
{
// 创建一个任务并使用默认任务调度器执行
Task task = Task.Factory.StartNew(() =>
{
Console.WriteLine("任务在默认任务调度器上执行。");
});
// 等待任务完成
task.Wait();
Console.WriteLine("任务执行完成。");
}
}
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
// 获取当前同步上下文的任务调度器
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
// 创建一个任务并在 UI 线程上执行
await Task.Factory.StartNew(() =>
{
// 更新 UI 控件
label1.Text = "任务在 UI 线程上执行。";
}, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
}
}
}
如果内置的任务调度器无法满足需求,可以通过继承 TaskScheduler
类来创建自定义任务调度器。以下是一个简单的自定义任务调度器示例,它限制了并发任务的数量:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
{
// 最大并发任务数量
private readonly int _maxDegreeOfParallelism;
// 任务队列
private readonly LinkedList<Task> _tasks = new LinkedList<Task>();
private int _delegatesQueuedOrRunning = 0;
public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
{
if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism));
_maxDegreeOfParallelism = maxDegreeOfParallelism;
}
protected override IEnumerable<Task> GetScheduledTasks()
{
bool lockTaken = false;
try
{
Monitor.TryEnter(_tasks, ref lockTaken);
if (lockTaken) return _tasks;
else throw new NotSupportedException();
}
finally
{
if (lockTaken) Monitor.Exit(_tasks);
}
}
protected override void QueueTask(Task task)
{
lock (_tasks)
{
_tasks.AddLast(task);
if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
{
++_delegatesQueuedOrRunning;
NotifyThreadPoolOfPendingWork();
}
}
}
private void NotifyThreadPoolOfPendingWork()
{
ThreadPool.UnsafeQueueUserWorkItem(_ =>
{
while (true)
{
Task item;
lock (_tasks)
{
if (_tasks.Count == 0)
{
--_delegatesQueuedOrRunning;
break;
}
item = _tasks.First.Value;
_tasks.RemoveFirst();
}
TryExecuteTask(item);
}
}, null);
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
if (taskWasPreviouslyQueued) return false;
return TryExecuteTask(task);
}
public override int MaximumConcurrencyLevel => _maxDegreeOfParallelism;
}
LimitedConcurrencyLevelTaskScheduler
类:继承自 TaskScheduler
,通过 _maxDegreeOfParallelism
字段限制并发任务的数量。QueueTask
方法:将任务添加到任务队列中,并根据当前并发任务数量决定是否将任务提交给线程池执行。NotifyThreadPoolOfPendingWork
方法:将任务从队列中取出并提交给线程池执行。TryExecuteTaskInline
方法:尝试在当前线程上直接执行任务。