任务并行库 (TPL) 基于任务的概念。术语“任务并行”是指同时运行的一个或多个任务。任务表示异步操作,在某些方面它类似于创建新线程或 ThreadPool 工作项,但抽象级别较高。任务提供两个主要好处:
出于这两个原因,在 .NET Framework 4 中,任务是用于编写多线程、异步和并行代码的首选 API。
1:隐式创建和运行任务
方法提供了一种简便方式,可同时运行任意数量的任意语句。只需为每个工作项传入 Action 委托即可。创建这些委托的最简单方式是使用 lambda 表达式。
Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());
为了更好地控制任务执行或从任务返回值,必须更加显式地使用 对象。
2:显式创建和运行任务
任务由 类表示。返回值的任务由 类表示,该类从 继承。
任务对象处理基础结构详细信息,并提供可在任务的整个生存期内从调用线程访问的方法和属性。 例如,可以随时访问任务的 属性,以确定它是已开始运行、已完成运行、已取消还是引发了异常。 状态由 枚举表示。 在创建任务时,您赋予它一个用户委托,该委托封装该任务将执行的代码。该委托可以表示为命名的委托、匿名方法或 lambda 表达式。lambda 表达式可以包含对命名方法的调用,如下面的示例所示。
代码 Task < byte [] > getData = new Task < byte [] > (() => GetFileData()); Task < double [] > analyzeData = getData.ContinueWith(x => Analyze(x.Result)); Task < string > reportData = analyzeData.ContinueWith(y => Summarize(y.Result)); getData.Start(); // or... Task < string > reportData2 = Task.Factory.StartNew(() => GetFileData()) .ContinueWith((x) => Analyze(x.Result)) .ContinueWith((y) => Summarize(y.Result)); System.IO.File.WriteAllText( @" C:\reportFolder\report.txt " , reportData.Result);
使用 ContinueWhenAll()()() 方法和 ContinueWhenAny()()() 方法,可以从多个任务继续。有关更多信息,请参见和。
类型和 类型提供使您可以等待任务完成的 Wait 方法的一些重载。此外,静态 TaskWaitAll()()() 方法和 TaskWaitAny()()() 方法的重载使您可以等待任务数组的任一任务或所有任务完成。
通常,会出于以下某个原因等待任务:
-
主线程依赖于任务计算的最终结果。
-
您必须处理可能从任务引发的异常。
代码 private void buttonParallelTaskWait_Click( object sender, RoutedEventArgs e) { // Wait on a single task with no timeout specified. Task task1 = Task.Factory.StartNew(() => DoSomeWork( 10000000 )); task1.Wait(); ConsoleTexter.Clear(); ConsoleTexter.WriteLine( " task1 has completed. " ); // Wait on a single task with a timeout specified. Task task2 = Task.Factory.StartNew(() => DoSomeWork( 10000000 )); task2.Wait( 100 ); // Wait for 100 ms. if (task2.IsCompleted) ConsoleTexter.WriteLine( " task2 has completed. " ); else ConsoleTexter.WriteLine( " Timed out before task2 completed. " ); // Wait for all tasks to complete. Task[] tasks = new Task[ 10 ]; for ( int i = 0 ; i < 10 ; i ++ ) { tasks[i] = Task.Factory.StartNew(() => DoSomeWork( 10000000 )); } Task.WaitAll(tasks); // Wait for first task to complete. Task < double > [] tasks2 = new Task < double > [ 3 ]; // Try three different approaches to the problem. Take the first one. tasks2[ 0 ] = Task < double > .Factory.StartNew(() => TrySolution1()); tasks2[ 1 ] = Task < double > .Factory.StartNew(() => TrySolution2()); tasks2[ 2 ] = Task < double > .Factory.StartNew(() => TrySolution3()); int index = Task.WaitAny(tasks2); double d = tasks2[index].Result; ConsoleTexter.WriteLine( " task[{0}] completed first with result of {1}. " , index, d); MessageBox.Show(ConsoleTexter.Out.ToString()); }
当某个任务引发一个或多个异常时,异常包装在 AggregateException 中。该异常传播回与该任务联接的线程,此线程通常是正在等待该任务或尝试访问该任务的 Result 属性的线程。此行为用于强制实施所有未处理的异常默认情况下应关闭进程的 .NET Framework 策略。调用代码可以通过在任务或任务组上使用 、WaitAll()()() 或 WaitAny()()() 方法或 Result()()() 属性,或者通过在 try-catch 块中包括 Wait 方法,来处理异常。
联接线程也可以通过在对任务进行垃圾回收之前访问 Exception 属性来处理异常。通过访问此属性,可防止未处理的异常触发在对象完成时关闭进程的异常传播行为。
异常处理的例子见下文的取消任务。
6:取消任务 和 类支持通过使用取消标记(.NET Framework 4 中的新功能)进行取消。有关更多信息,请参见。在任务的类中,取消涉及到表示可取消操作的用户委托与请求取消的代码之间的协作。 成功的取消涉及到调用 方法的请求代码,以及及时终止操作的用户委托。可以使用以下选项之一终止操作:
-
简单地从委托中返回。在许多情况下,这样已足够;但是,采用这种方式“取消”的任务实例会转换为 RanToCompletion 状态,而不是 Canceled 状态。
-
引发 OperationCanceledException,并将其传递到在其上请求了取消的标记。完成此操作的首选方式是使用 ThrowIfCancellationRequested 方法。采用这种方式取消的任务会转换为 Canceled 状态,调用代码可使用该状态来验证任务是否响应了其取消请求。
代码 CancellationTokenSource tokenSource2; CancellationToken ct; Task task; public UserControlParallel() { InitializeComponent(); tokenSource2 = new CancellationTokenSource(); ct = tokenSource2.Token; } // 任务开始 private void buttonParallelTaskStart_Click( object sender, RoutedEventArgs e) { task = Task.Factory.StartNew(() => { // Were we already canceled? ct.ThrowIfCancellationRequested(); bool moreToDo = true ; while (moreToDo) { // Poll on this property if you have to do // other cleanup before throwing. Thread.Sleep( 100 ); if (ct.IsCancellationRequested) { // Clean up here, then... ct.ThrowIfCancellationRequested(); } } }, tokenSource2.Token); // Pass same token to StartNew. } // 任务结束 private void buttonParallelTaskCancel_Click( object sender, RoutedEventArgs e) { tokenSource2.Cancel(); // Just continue on this thread, or Wait/WaitAll with try-catch: try { task.Wait(); } catch (AggregateException err) { ConsoleTexter.Clear(); foreach (var v in err.InnerExceptions) ConsoleTexter.WriteLine( " msg: " + v.Message); MessageBox.Show(ConsoleTexter.Out.ToString()); } }