Task Parallel Library(TPL)是 .NET 中构建并行和异步应用的核心框架。基础教程中我们接触了 Task 的基本用法和 async/await,但 TPL 的能力远不止于此。本文将深入探讨 Task 的底层机制、Parallel 类、PLINQ、Dataflow 库、以及如何正确控制并行度与取消。
Task 底层细节
Task 状态机
1 2 3 4 5 6 7 8 9
| Task task = new Task(() => Thread.Sleep(100)); Console.WriteLine(task.Status);
task.Start(); Console.WriteLine(task.Status);
task.Wait(); Console.WriteLine(task.Status);
|
TaskCreationOptions 与 TaskScheduler
1 2 3 4 5 6 7 8 9 10
| Task longRunning = Task.Factory.StartNew(() => { Thread.Sleep(TimeSpan.FromHours(1)); }, TaskCreationOptions.LongRunning);
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); Task.Factory.StartNew(() => UpdateUI(), CancellationToken.None, TaskCreationOptions.None, uiScheduler);
|
父子任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Task parent = Task.Factory.StartNew(() => { Console.WriteLine("父任务开始");
Task child = Task.Factory.StartNew(() => { Thread.Sleep(500); Console.WriteLine("子任务完成"); }, TaskCreationOptions.AttachedToParent);
Console.WriteLine("父任务结束"); });
parent.Wait(); Console.WriteLine("所有任务完成");
|
Parallel 类
Parallel.For 与 Parallel.ForEach
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| Parallel.For(0, 100, i => { Console.WriteLine($"处理 {i},线程 {Thread.CurrentThread.ManagedThreadId}"); });
var items = Enumerable.Range(1, 1000); Parallel.ForEach(items, item => { Compute(item); });
var options = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount / 2, CancellationToken = cancellationToken };
Parallel.For(0, 100, options, i => { cancellationToken.ThrowIfCancellationRequested(); Process(i); });
|
Parallel.Invoke
1 2 3 4 5
| Parallel.Invoke( () => ProcessImage("image1.jpg"), () => ProcessImage("image2.jpg"), () => GenerateThumbnail("image3.jpg") );
|
Break vs Stop
1 2 3 4 5 6 7 8
| Parallel.For(0, 100_000, (i, state) => { if (i > 5000) { state.Break(); } });
|
PLINQ(Parallel LINQ)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| var nums = Enumerable.Range(1, 1_000_000);
var parallelResult = nums.AsParallel() .Where(n => IsPrime(n)) .Select(n => n * n) .ToArray();
var orderedResult = nums.AsParallel() .AsOrdered() .Where(n => n % 2 == 0) .ToArray();
var forceParallel = nums.AsParallel() .WithExecutionMode(ParallelExecutionMode.ForceParallelism) .Where(n => ExpensiveCheck(n));
var controlled = nums.AsParallel() .WithDegreeOfParallelism(4) .Select(n => HeavyComputation(n)) .ToList();
var merge = nums.AsParallel() .WithMergeOptions(ParallelMergeOptions.NotBuffered) .Select(n => ExpensiveCompute(n));
foreach (var item in merge) { Console.WriteLine(item); }
|
Dataflow(TPL Dataflow)
System.Threading.Tasks.Dataflow 提供了基于 Actor 模型的数据流管道,非常适合生产者-消费者模式和流水线处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
var bufferBlock = new BufferBlock<int>();
var transformBlock = new TransformBlock<int, string>(n => { Thread.Sleep(100); return $"处理结果: {n * 2}"; }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4, BoundedCapacity = 100 });
var actionBlock = new ActionBlock<string>(result => { Console.WriteLine(result); });
bufferBlock.LinkTo(transformBlock, new DataflowLinkOptions { PropagateCompletion = true }); transformBlock.LinkTo(actionBlock, new DataflowLinkOptions { PropagateCompletion = true });
for (int i = 0; i < 20; i++) { await bufferBlock.SendAsync(i); }
bufferBlock.Complete(); await actionBlock.Completion;
|
常用 Dataflow 块
| 类型 |
行为 |
BufferBlock<T> |
先进先出队列 |
TransformBlock<TInput, TOutput> |
转换每个元素 |
ActionBlock<T> |
对每个元素执行操作 |
BatchBlock<T> |
将 N 个元素打包为一个批次 |
JoinBlock<T1, T2> |
等待两个来源匹配后合并 |
BroadcastBlock<T> |
广播给所有目标 |
示例:批处理并行下载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| var downloadBlock = new TransformBlock<string, (string url, byte[] data)>( async url => (url, await new HttpClient().GetByteArrayAsync(url)), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 8 } );
var batchBlock = new BatchBlock<(string, byte[])>(10); var saveBlock = new ActionBlock<(string, byte[])[]>(async batch => { foreach (var (url, data) in batch) { var fileName = Path.GetFileName(url); await File.WriteAllBytesAsync(fileName, data); } });
downloadBlock.LinkTo(batchBlock, new DataflowLinkOptions { PropagateCompletion = true }); batchBlock.LinkTo(saveBlock, new DataflowLinkOptions { PropagateCompletion = true });
foreach (var url in urls) { await downloadBlock.SendAsync(url); } downloadBlock.Complete(); await saveBlock.Completion;
|
取消令牌(CancellationToken)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| using var cts = new CancellationTokenSource(); CancellationToken token = cts.Token;
var task = Task.Run(() => { for (int i = 0; i < 100; i++) { token.ThrowIfCancellationRequested(); Thread.Sleep(200); } }, token);
cts.CancelAfter(5000);
try { await task; } catch (OperationCanceledException) { Console.WriteLine("任务已取消"); }
|
链接取消令牌
1 2 3 4 5 6
| using var appCts = new CancellationTokenSource(); using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( appCts.Token, timeoutCts.Token);
await Task.Run(() => DoWork(linkedCts.Token), linkedCts.Token);
|
Task 组合模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| var tasks = Enumerable.Range(0, 10).Select(i => ProcessAsync(i)); await Task.WhenAll(tasks);
var first = await Task.WhenAny(tasks); if (first.Status == TaskStatus.RanToCompletion) { await first; }
static async Task<T[]> WhenAllThrottled<T>( IEnumerable<Func<CancellationToken, Task<T>>> taskFactories, int maxConcurrency, CancellationToken token = default) { var semaphore = new SemaphoreSlim(maxConcurrency); var tasks = taskFactories.Select(async factory => { await semaphore.WaitAsync(token); try { return await factory(token); } finally { semaphore.Release(); } }); return await Task.WhenAll(tasks); }
|
异常处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| Task task = Task.Run(() => { throw new InvalidOperationException("出错了"); throw new AccessViolationException("又出错了"); });
try { await task; } catch (AggregateException ae) when (task.IsFaulted) { foreach (var ex in ae.InnerExceptions) { Console.WriteLine(ex.Message); } }
task.ContinueWith(t => { foreach (var ex in t.Exception?.InnerExceptions ?? Enumerable.Empty<Exception>()) { Console.WriteLine($"异常: {ex.Message}"); } }, TaskContinuationOptions.OnlyOnFaulted);
|
总结:TPL 是 .NET 并行编程的现代基石。Parallel 类适合 CPU 密集型数据并行,PLINQ 提供声明式并行查询,Dataflow 库适合复杂的流水线和生产者-消费者模式。理解 Task 的底层机制和各类组合模式,可以帮助我们编写可预测、可取消、可扩展的并行代码。