《C#本质论》读书笔记(18)多线程处理

.NET Framework 4.0 看(本质论第3版)
.NET Framework 4.5 看(本质论第4版)

.NET 4.0为多线程引入了两组新API:TPL(Task Parallel Library,任务并行库)和PLINQ(Parallel LINQ,并行LINQ)。

18.1 独立线程的运行和控制

通过 System.Threading.Tasks.Task 类在托管代码中公开各种API,该类代表的是一个异步操作。 然而,一个 Task 不直接映射 到一个非托管线程。相反, Task 为底层的非托管线程构造提供了一定程度的 抽象

不是每次创建安一个  Task  时会创建一个线程。相反,  Task  会从 线程池 请求一个线程。线程池针对 Task 请求,会判断是否需要创建一个全新线程,还是分配一个现有的线程。

通过将线程概念抽象为  Task  ,开发人员不必操心何时创建一个新的操作系统线程,何时重用一个现有线程。换言之, 降低了高效管理线程所涉及的复杂性

 编写 Task 时,需要分配希望 Task 执行的一组指令,然后启动 Task 。指令的分配基本上都靠委托。
      
  1. class Program  
  2. {  
  3.     static void Main(string[] args)  
  4.     {  
  5.         const int Repettitions = 10000;  
  6.         Task task = new Task(() =>  
  7.         {  
  8.             for (int count = 0; count < Repettitions; count++)  
  9.             {  
  10.                 Console.Write('-');  
  11.             }  
  12.         });  
  13.         task.Start();  
  14.         for (int count = 0; count < Repettitions; count++)  
  15.         {  
  16.             Console.Write('+');  
  17.         }  
  18.   
  19.         task.Wait();  
  20.     }  
  21. }  


程序在声明了 Task 之后,执行了一个 Start() 调用。除非执行这个调用,否则为 Task 指定的 Action 是不会开始执行的。  task.Wait() 调用强迫主线程(正在执行的第2个 for 循环线程)停止,并“等待”分配给 task 的所有工作执行完。

问题:我什么时候能使用Task的Start()方法?

  1. 问题:我什么时候能使用Task的Start()方法?只有 Task 处于 TaskStatus.Created 状态时才能使用实例方法 Start() 。
    并且,只有在使用 Task 的
    公共构造函数构造的 Task 实例才能处于 TaskStatus.Created 状态。

  2. 问题:使用Task.Run()/Task.ContinueWith()/Task.Factory.StartNew()/TaskCompletionSource/异步方法(即使用asyncawait关键字的方法)……应该调用Start()方法吗?
    不应该。不仅不应该,而且也不能,因为此时调用Start()会报异常。从问题1可知:Start()实例方法只适用于TaskStatus.Created状态的Task。由上面提到的方式创建的Task其状态不是TaskStatus.Created,而是如TaskStatus.WaitingForActivation、TaskStatus.Running或TaskStatus.RanToCompletion。

如果在任务执行的操作要返回一个结果,那么对结果的任何请求都会被自动阻塞(block),直至任务完成。

  1. static void Main(string[] args)  
  2. {  
  3.     Task<string> task = Task.Factory.StartNew<string>(  
  4. > PiCalculator.Calculate(100));  
  5.   
  6.     foreach (char busySymbol in Utility.BusySymbols())  
  7.     {  
  8.         if (task.IsCompleted)  
  9.         {  
  10.             Console.Write('\b');  
  11.             break;  
  12.         }  
  13.         Console.Write(busySymbol);  
  14.     }  
  15.   
  16.     Console.WriteLine();  
  17.     // Blocks until task completes.  
  18.     Console.WriteLine(task.Result);  
  19.     System.Diagnostics.Trace.Assert(task.IsCompleted);  
  20. }  

这个代码显示的任务数据类型是 Task<TResult> (本例具体是一个string)。任务的泛型版本包含一个 Result 属性,可通过它获取 Task<TResult> 所执行的 Func<TResult> 的返回值。
             
  
代码中没有 task.Start() 调用,使用了静态 Factory 属性的 StartNew() 方法。结果和实例化 Task 差不多,只是从 Task.Factory.StartNew<TResult>(Func<TResult> function) 返回后,线程已经启动了。是同  StartNew() 能满足你的几乎一切要求,除非要将 Task 的实例化和调度分开。

Task 的属性
属性 解释
说明
Status 指明任务的状态。
Created = 0,
WaitingForActivation = 1,
WaitingToRun = 2,
Running = 3,
WaitingForChildrenToComplete = 4,
RanToCompletion = 5,
Canceled = 6,
Faulted = 7,
获取此任务的 TaskStatus 枚举
IsComplete 不管任务是否出错,IsComplete在任务完成后都被设为true,任何时候只要Status为 5,6,7 就为true。 获取此 Task 是否已完成
Id 解决多线程问题(竞争和死锁),Id尤为有用 获取此 Task 实例的 ID
AsyncState 能跟踪额外的数据。 获取在创建 Task 时提供的状态对象,如果未提供,则为 null
Task.CurrentId 任何地方都可调用,特别适合调试和诊断性类型的活动 返回当前正在执行 Task 的 ID

  AyncState 举例: 假定多个任务要计算一个 List<T> 的值。为了将结果放在列表中正确位置。 将准备包含结果的那个列表索引村吃到 AsyncState 属性中。这样在任务结束后,代码可以使用  AsyncState  (先转换成 int)访问列表中特定索引位置。(注意,调用 List.Add() 不是一个跨多线程的安全操作,可能造成竞态条件,进而造成数据丢失。)

Task声明周期
《C#本质论》读书笔记(18)多线程处理_第1张图片

  1. public enum TaskStatus  
  2. {   
  3.     // 该任务已初始化,但尚未被计划。  
  4.     Created = 0,  
  5.     // 该任务正在等待 .NET Framework 基础结构在内部将其激活并进行计划。  
  6.     WaitingForActivation = 1,  
  7.     // 该任务已被计划执行,但尚未开始执行。  
  8.     WaitingToRun = 2,  
  9.    
  10.     // 该任务正在运行,但尚未完成。  
  11.     Running = 3,  
  12.     // 该任务已完成执行,正在隐式等待附加的子任务完成。  
  13.     WaitingForChildrenToComplete = 4,  
  14.    
  15.     // 已成功完成执行的任务。  
  16.     RanToCompletion = 5,  
  17.     // 该任务已通过对其自身的 CancellationToken 引发 OperationCanceledException 异常  
  18.     Canceled = 6,  
  19.     // 由于未处理异常的原因而完成的任务。  
  20.     Faulted = 7,  
  21. }  
 
《C#本质论》读书笔记(18)多线程处理_第2张图片
  《C#本质论》读书笔记(18)多线程处理_第3张图片
 
  1. class Program  
  2. {  
  3.     static void Main(string[] args)  
  4.     {  
  5.         /*  创建一个任务 不调用 不执行  状态为Created */  
  6.         Task tk = new Task(() =>  
  7.         {  
  8.         });  
  9.         Console.WriteLine(tk.Status.ToString());  
  10.   
  11.         /*  创建一个任务 执行  状态为 WaitingToRun */  
  12.         Task tk1 = new Task(() =>  
  13.         {  
  14.         });  
  15.         tk1.Start(); /*对于安排好的任务,就算调用Start方法也不会立马启动 此时任务的状态为WaitingToRun*/  
  16.         Console.WriteLine(tk1.Status.ToString());  
  17.   
  18.         /*  创建一个主任务 */  
  19.         Task mainTask = new Task(() =>  
  20.         {  
  21.             SpinWait.SpinUntil(() =>  
  22.             {  
  23.                 return false;  
  24.             }, 30000);  
  25.         });  
  26.             
  27.         /*  将子任务加入到主任务完成之后执行 */  
  28.         Task subTask = mainTask.ContinueWith((t1) =>  
  29.         {  
  30.         });  
  31.         /*  启动主任务 */  
  32.         mainTask.Start();  
  33.         /*  此时子任务状态为 WaitingForActivation */  
  34.         Console.WriteLine(subTask.Status.ToString());  
  35.   
  36.   
  37.         /*  创建一个任务 执行 后 等待一段时间 并行未结束的情况下 状态为 Running */  
  38.         Task tk2 = new Task(() =>  
  39.         {  
  40.             SpinWait.SpinUntil(() => false, 30000);  
  41.         });  
  42.         tk2.Start(); /*对于安排好的任务,就算调用Start方法也不会立马启动*/  
  43.         SpinWait.SpinUntil(() => false, 300);  
  44.         Console.WriteLine(tk2.Status.ToString());  
  45.   
  46.   
  47.         /*  创建一个任务 然后取消该任务 状态为Canceled */  
  48.         CancellationTokenSource cts = new CancellationTokenSource();  
  49.         Task tk3 = new Task(() =>  
  50.         {  
  51.             for (int i = 0; i < int.MaxValue; i++)  
  52.             {  
  53.                 if (!cts.Token.IsCancellationRequested)  
  54.                 {  
  55.                     cts.Token.ThrowIfCancellationRequested();  
  56.                 }  
  57.             }  
  58.         }, cts.Token);  
  59.         tk3.Start(); /*启动任务*/  
  60.         SpinWait.SpinUntil(() => false, 100);  
  61.         cts.Cancel(); /*取消该任务执行 但并非立马取消 所以对于Canceled状态也不会立马生效*/  
  62.         SpinWait.SpinUntil(() => false, 1000);  
  63.         Console.WriteLine(tk3.Status.ToString() + " " + tk3.IsCanceled);  
  64.         SpinWait.SpinUntil(() => false, 1000);  
  65.         Console.WriteLine(tk3.Status.ToString() + " " + tk3.IsCanceled);  
  66.         SpinWait.SpinUntil(() => false, 1000);  
  67.         Console.WriteLine(tk3.Status.ToString() + " " + tk3.IsCanceled);  
  68.   
  69.         /*创建一个任务 让它成功的运行完成 会得到 RanToCompletion 状态*/  
  70.         Task tk4 = new Task(() =>  
  71.         {  
  72.             SpinWait.SpinUntil(() => false, 10);  
  73.         });  
  74.         tk4.Start();  
  75.         SpinWait.SpinUntil(() => false, 300);  
  76.         Console.WriteLine(tk4.Status.ToString());  
  77.   
  78.         /*创建一个任务 让它运行失败 会得到 Faulted 状态*/  
  79.         Task tk5 = new Task(() =>  
  80.         {  
  81.             throw new Exception();  
  82.         });  
  83.         tk5.Start();  
  84.         SpinWait.SpinUntil(() => false, 300);  
  85.         Console.WriteLine(tk5.Status.ToString());  
  86.   
  87.         Console.ReadLine();  
  88.     }  
  89. }  
  90.   
  91. class Product  
  92. {  
  93.     public string Name { getset; }  
  94.     public string Category { getset; }  
  95.     public int SellPrice { getset; }  
  96. }  


《C#本质论》读书笔记(18)多线程处理_第4张图片
 

18.1.1 ContinueWith()

Task 包含一个ContinueWith()方法,它的作用是将任务链接起来。

18.1.2 task上未处理的异常

Task执行期间产生的未处理异常会被禁止(suppressed),知道调用某个任务完成成员:Wait()、Result、Task.WaitAll()或者Task.WaitAny()。

18.1.3 取消任务

.NET4之前没有支持取消请求。相反,只能依赖一种“野蛮”中断方式。

18.1.4 长时间运行任务

如果开发人员知道一个Task要长时间运行,会长时间“霸占”一个底层线程资源,开发人员应告诉线程池共享线程不会太快交还。线程池更有可能为任务创建一个专用线程(而不是分配其中一个共享线程)。为此,调用StartNew()时,要使用 TaskCreationOptions.LongRunning 选项。

18.1.5 释放一个任务

当程序开始退出的时候,如果Task仍在运行,Task所依赖的底层线程会被CLR终止。所以,因为线程终止而造成的不好的结果可能会在应用程序退出时发生。首选方案是协作式取消,Task支持取消,而应用程序会调用取消,并等待任务结束。

18.2 并行迭代


18.2.1 使用 System.AggregateException 进行并行异常处理

18.2.2 取消并行循环




18.3 并行执行LINQ查询




18.4 .NET4.0之前的多线程处理


《learning hard C#学习笔记》读书笔记(19)多线程

18.5 AppPomain 的未处理异常






参考:
(译).NET4.X 并行任务中Task.Start()的FAQ




null


你可能感兴趣的:(《C#本质论》读书笔记(18)多线程处理)