.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. task.Wait();
  19. }
  20. }


程序在声明了 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/异步方法(即使用async与await关键字的方法)……应该调用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. foreach (char busySymbol in Utility.BusySymbols())
  6. {
  7. if (task.IsCompleted)
  8. {
  9. Console.Write('\b');
  10. break;
  11. }
  12. Console.Write(busySymbol);
  13. }
  14. Console.WriteLine();
  15. // Blocks until task completes.
  16. Console.WriteLine(task.Result);
  17. System.Diagnostics.Trace.Assert(task.IsCompleted);
  18. }

这个代码显示的任务数据类型是 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声明周期

  1. public enum TaskStatus
  2. {
  3. // 该任务已初始化,但尚未被计划。
  4. Created = 0,
  5. // 该任务正在等待 .NET Framework 基础结构在内部将其激活并进行计划。
  6. WaitingForActivation = 1,
  7. // 该任务已被计划执行,但尚未开始执行。
  8. WaitingToRun = 2,
  9. // 该任务正在运行,但尚未完成。
  10. Running = 3,
  11. // 该任务已完成执行,正在隐式等待附加的子任务完成。
  12. WaitingForChildrenToComplete = 4,
  13. // 已成功完成执行的任务。
  14. RanToCompletion = 5,
  15. // 该任务已通过对其自身的 CancellationToken 引发 OperationCanceledException 异常
  16. Canceled = 6,
  17. // 由于未处理异常的原因而完成的任务。
  18. Faulted = 7,
  19. }
 

 
 
  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. /*  创建一个任务 执行  状态为 WaitingToRun */
  11. Task tk1 = new Task(() =>
  12. {
  13. });
  14. tk1.Start(); /*对于安排好的任务,就算调用Start方法也不会立马启动 此时任务的状态为WaitingToRun*/
  15. Console.WriteLine(tk1.Status.ToString());
  16. /*  创建一个主任务 */
  17. Task mainTask = new Task(() =>
  18. {
  19. SpinWait.SpinUntil(() =>
  20. {
  21. return false;
  22. }, 30000);
  23. });
  24. /*  将子任务加入到主任务完成之后执行 */
  25. Task subTask = mainTask.ContinueWith((t1) =>
  26. {
  27. });
  28. /*  启动主任务 */
  29. mainTask.Start();
  30. /*  此时子任务状态为 WaitingForActivation */
  31. Console.WriteLine(subTask.Status.ToString());
  32. /*  创建一个任务 执行 后 等待一段时间 并行未结束的情况下 状态为 Running */
  33. Task tk2 = new Task(() =>
  34. {
  35. SpinWait.SpinUntil(() => false, 30000);
  36. });
  37. tk2.Start(); /*对于安排好的任务,就算调用Start方法也不会立马启动*/
  38. SpinWait.SpinUntil(() => false, 300);
  39. Console.WriteLine(tk2.Status.ToString());
  40. /*  创建一个任务 然后取消该任务 状态为Canceled */
  41. CancellationTokenSource cts = new CancellationTokenSource();
  42. Task tk3 = new Task(() =>
  43. {
  44. for (int i = 0; i < int.MaxValue; i++)
  45. {
  46. if (!cts.Token.IsCancellationRequested)
  47. {
  48. cts.Token.ThrowIfCancellationRequested();
  49. }
  50. }
  51. }, cts.Token);
  52. tk3.Start(); /*启动任务*/
  53. SpinWait.SpinUntil(() => false, 100);
  54. cts.Cancel(); /*取消该任务执行 但并非立马取消 所以对于Canceled状态也不会立马生效*/
  55. SpinWait.SpinUntil(() => false, 1000);
  56. Console.WriteLine(tk3.Status.ToString() + " " + tk3.IsCanceled);
  57. SpinWait.SpinUntil(() => false, 1000);
  58. Console.WriteLine(tk3.Status.ToString() + " " + tk3.IsCanceled);
  59. SpinWait.SpinUntil(() => false, 1000);
  60. Console.WriteLine(tk3.Status.ToString() + " " + tk3.IsCanceled);
  61. /*创建一个任务 让它成功的运行完成 会得到 RanToCompletion 状态*/
  62. Task tk4 = new Task(() =>
  63. {
  64. SpinWait.SpinUntil(() => false, 10);
  65. });
  66. tk4.Start();
  67. SpinWait.SpinUntil(() => false, 300);
  68. Console.WriteLine(tk4.Status.ToString());
  69. /*创建一个任务 让它运行失败 会得到 Faulted 状态*/
  70. Task tk5 = new Task(() =>
  71. {
  72. throw new Exception();
  73. });
  74. tk5.Start();
  75. SpinWait.SpinUntil(() => false, 300);
  76. Console.WriteLine(tk5.Status.ToString());
  77. Console.ReadLine();
  78. }
  79. }
  80. class Product
  81. {
  82. public string Name { get; set; }
  83. public string Category { get; set; }
  84. public int SellPrice { get; set; }
  85. }


 

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之前的多线程处理



18.5 AppPomain 的未处理异常






参考:


《C#本质论》读书笔记(18)多线程处理的更多相关文章

  1. 存储器的保护(一)——《x86汇编语言:从实模式到保护模式》读书笔记18

    本文是原书第12章的学习笔记. 说句题外话,这篇博文是补写的,因为让我误删了,可恶的是CSDN的回收站里找不到! 好吧,那就再写一遍,我有坚强的意志.司马迁曰:“文王拘而演<周易>:仲尼厄 ...

  2. C#本质论读书笔记:第一章 C#概述|第二章 数据类型

    第一章 1.字符串是不可变的:所有string类型的数据,都不可变,也可以说是不可修改的,不能修改变量最初引用的数据,只能对其重新赋值,让其指向内存中的一个新位置. 第二章 2.1 预定义类型或基本类 ...

  3. 《Linux内核设计与实现》读书笔记 18

    第十八章调试 18.1 准备开始 一个bug:大部分bug通常都不是行为可靠而且定义明确的 一个藏匿bug的内核版本:找出bug首先出现的版本 相关内核代码的知识和运气 18.2内核中的bug 可以有 ...

  4. 『TCP/IP详解——卷一:协议』读书笔记——18

    2013-08-27 15:44:52 第7章 Ping程序 7.1 引言 “ping”这个名字来源于声纳定为操作.Ping程序由Mike Muuss编写,目的是为了测试另一台主机是否可达.该程序发送 ...

  5. OCP读书笔记(18) - 空间管理

    OLTP 表压缩 压缩始终是非常占用CPU的过程,并且需要花费一定时间,通常,如果压缩数据,则数据必须解压缩后才能使用.虽然此要求在数据仓库环境中是可以接受的但在OLTP环境中可能无法接受 现在,在O ...

  6. OCA读书笔记(18) - 使用Support工具

    调查和解决问题 问题:数据库中的任一严重的错误定义为一个问题,一般来说,这些错误包括大家熟悉的ORA-600错误和ORA-04031(共享池超出)错误,涉及数据库问题的所有元数据都存储在ADR中,每个 ...

  7. 《http权威指南》读书笔记18

    概述 最近对http很感兴趣,于是开始看<http权威指南>.别人都说这本书有点老了,而且内容太多.我个人觉得这本书写的太好了,非常长知识,让你知道关于http的很多概念,不仅告诉你怎么做 ...

  8. 存储器的保护(三)——《x86汇编语言:从实模式到保护模式》读书笔记20

    存储器的保护(三) 修改本章代码清单,使之可以检测1MB以上的内存空间(从地址0x0010_0000开始,不考虑高速缓存的影响).要求:对内存的读写按双字的长度进行,并在检测的同时显示已检测的内存数量 ...

  9. 《TCP/IP详解卷1:协议》第17、18章 TCP:传输控制协议(1)-读书笔记

    章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...

随机推荐

  1. Linux系统下设置环境变量

    例如我现在有一个软件understand代码审阅软件,现在我每次想要打开这个软件就要进到~/scitools/bin/linux32目录下,去执行可执行文件understand; 但每次都这样我会觉得 ...

  2. ionic 发布 inoc显示不正确

    前两天因为学习的问题,把本地环境给搞崩了,然后重新安装环境之后发现生成的安装包不能使用,然后找了很多原因都不能解决,因为之前发布ios的时候使用命令 ionic resources的时候就可以将图标显 ...

  3. cookie---session

    //以下文字摘自慕课网教程..... 设置cookie PHP设置Cookie最常用的方法就是使用setcookie函数,setcookie具有7个可选参数,我们常用到的为前5个: name( Coo ...

  4. linux 主机名

    查看主机名命令:# uname -n csdba #hostname csdba 1.通过hostname命令.命令格式:hostname newhostname 此命令的作用是暂时的修改linux的 ...

  5. 【.net core 跨平台】第一步 在Ubuntu16.04 配置.net core环境

    本次使用VMware10.0.4工具安装Ubuntu16.04系统并配置.net core环境   Ubuntu 16.04 desktop下载地址:http://releases.ubuntu.co ...

  6. Python PIP安装

    https://zhidao.baidu.com/question/550936793.html 按图做

  7. 常见ES5方法

    • ES5 JSON扩展JSON.parseJSON.stringify • ES5 Object扩展Object.createObject.keys • Date对象Date.now • ES5 F ...

  8. AngularJS 依赖注入

        依赖注入(Dependency Injection,简称DI)是一种软件设计模式,在这种模式下,一个或更多的依赖(或服务)被注入(或者通过引用传递)到一个独立的对象(或客户端)中,然后成为了该 ...

  9. LODProp3D实例

    1. Level of detail(LoD)多细节层次描述(简称LoD)是实时绘制复杂几何场景的一种有效工具.基于层次结构的动态简化方法能够根据视点的变化,实时连续地转换场景细节模型.在本例中,实现 ...

  10. 个人对RCU的理解

    本文对于RCU的概念不进行解释. 考虑一种比较让人困惑的情形,就是在grace period期间,有新的读者进入,那么这个读者拿到的是新数据还是旧数据,查阅了很多资料都没找到答案,当然对于链表的情况这 ...