前面一篇提到例子都是数据并行,但这并不是并行化的唯一形式,在.Net4之前,必须要创建多个线程或者线程池来利用多核技术。现在只需要使用新的Task实例就可以通过更简单的代码解决命令式任务并行问题。

1.Task及它的生命周期

一个Task表示一个异步操作,它的创建和执行都是独立的,因此可以对相关操作的执行拥有完全的控制权;当有很多异步操作作为Task实例加载的时候,为了充分利用运行时的逻辑内核,任务调度器会尝试并行的运行这些任务,当然任务都是有额外的开销,虽然要小于添加线程的开销;

对Task实例的生命周期的理解非常重要。一个Task的执行,取决于底层硬件和运行时可用的资源。因此Task实例的状态会不断的发生改变,而一个Task实例只会完成其生命周期一次,当Task到达它三种可能的最终状态只后,它就回不去之前的任何状态了。

Task实例有三种可能的初始状态,Created是Task构造函数创建实例的初始状态,WaitForActivation是子任务依赖其他任务完成后等待调度的初始状态,WaitingToRun是通过TaskFactory.StartNew所创建任务的初始状态。表示正在等待调度器挑选自己并运行。

任务开始执行,状态就变为TaskStatus.Runing。如果还有子任务,主任务的状态会转变到TaskStatus.WaitingForChildrenToComplete状态。并最终到达,Canceled,Faulted和RunToCompletion 三种状态。从字面理解就是任务取消,出错和完成。

2.任务并行。

前面我们通过Parallel.Invoke来并行加载方法。

  1. Parallel.Invoke(GenerateAESKeys,GenerateMD5Has);

通过Task实例也能完成同样的工作。

  1. var t1 = new Task(GenerateAESKeys);
  2. var t2 = new Task(GenerateMD5Has);
  3. t1.Start();
  4. t2.Start();
  5. Task.WaitAll(t1, t2);

Start方法对委托进行初始化。 WaitAll方法会等待两个任务的执行完成之后再往下走。

可以看见,执行过程中,任务的状态不断的发生变化。可以给WaitFor方法加上毫秒数。看任务是否会在指定时间内完成。

  1. if(!Task.WaitAll(new[]{t1,t2},))
  2. {
  3. Console.WriteLine("任务执行超过3秒");
  4. Console.WriteLine(t1.Status.ToString());
  5. Console.WriteLine(t2.Status.ToString());
  6. }

即使到达了指定时间,任务还是继续执行。

同样任务本身也是可以等待

  1. if (t1.Wait())
  2. {
  3. Console.WriteLine("任务t1执行超过3秒");
  4. Console.WriteLine(t1.Status.ToString());
  5. }

3.通过取消标记取消任务。

可以通过CancellationToken 来中断任务的执行。这需要再委托中添加一些代码,创建可以取消的任务。

  1. private static void GenerateMD5HasCancel(CancellationToken ct)
  2. {
  3. ct.ThrowIfCancellationRequested();
  4. var sw = Stopwatch.StartNew();
  5. for (int i = ; i < NUM_AES_KEYS; i++)
  6. {
  7. var md5M = MD5.Create();
  8. byte[] data = Encoding.Unicode.GetBytes(Environment.UserName + i);
  9. byte[] result = md5M.ComputeHash(data);
  10. string hexString = ConverToHexString(result);
  11. ct.ThrowIfCancellationRequested();
  12. }
  13. Console.WriteLine("MD5:" + sw.Elapsed.ToString());
  14. }
  1. Console.WriteLine("任务开始...");
  2. var cts = new CancellationTokenSource();
  3. var ct = cts.Token;
  4. var sw = Stopwatch.StartNew();
  5. var t1 = Task.Factory.StartNew(() => GenerateMD5HasCancel(ct), ct);
  6. var t2 = Task.Factory.StartNew(() => GenerateAESKeysCancel(ct), ct);
  7.  
  8. //1秒后取消任务
  9. Thread.Sleep();
  10.  
  11. cts.Cancel();
  12.  
  13. try
  14. {
  15. if (!Task.WaitAll(new[] { t1,t2}, ))
  16. {
  17. Console.WriteLine("任务执行超过1秒");
  18. Console.WriteLine(t1.Status.ToString());
  19. }
  20. }
  21. catch (AggregateException ex)
  22. {
  23. foreach (var exc in ex.InnerExceptions)
  24. {
  25. Console.WriteLine(exc.ToString());
  26. }
  27. if (t1.IsCanceled)
  28. {
  29. Console.WriteLine("任务1取消了...");
  30. }
  31. Console.WriteLine(sw.Elapsed.ToString());
  32. Console.WriteLine("结束");
  33. }
  1. CancellationTokenSource能够初始化取消的请求,而CancellationToken能将这些请求传递给异步操作;上面的方法通过Task类的Factory方法得到一个TaskFactory实例,相比Task直接创建任务,这个实例可以使用更多的功能。而StartNew 等价于用Task构造函数创建一个Task并调用Start方法执行。

直接在Debug下面运行,程序会在异常的地方中断。直接运行exe得到上面的结果。

ThrowIfCancellationRequested在每一次循环迭代都会执行,内部是判断任务取消后抛出一个OperationCanceledException的异常,来避免运行不必要的循环和其他命令。

  1. public void ThrowIfCancellationRequested()
  2. {
  3. if (IsCancellationRequested)
  4. ThrowOperationCanceledException();
  5. }
  6. private void ThrowOperationCanceledException()
  7. {
  8. throw new OperationCanceledException(Environment.GetResourceString("OperationCanceled"), this);
  9. }

如果有代码正在等待取消,还会自动抛出一个TaskCanceledException异常。会包含在AggregateException中。

4.处理异常。

修改上面的方法抛出一个异常。

  1. private static void GenerateMD5HasCancel(CancellationToken ct)
  2. {
  3. ct.ThrowIfCancellationRequested();
  4. //....if (sw.Elapsed.TotalSeconds > 0.5)
  5. {
  6. throw new TimeoutException("超时异常0.5秒");
  7. }
  8. ct.ThrowIfCancellationRequested();
  9. }
  10. Console.WriteLine("MD5:" + sw.Elapsed.ToString());
  11. }

修改Main方法的Catch。

  1. if (t1.IsFaulted)
  2. {
  3. foreach (var exc in ex.InnerExceptions)
  4. {
  5. Console.WriteLine(exc.ToString());
  6. }
  7. Console.WriteLine(t1.Status.ToString());
  8. }

执行结果:

当出现异常时,任务的状态就会转换为Faulted。并不会影响另外一个任务的执行。

5.从任务返回值。

前面的方法都是没有返回值,得到任务的返回值需要使用Task<TResult>实例,TResult要替换为返回的类型。修改AES方法。返回一个指定前缀的List<String>

  1. GenerateMD5HasList
  1. private static List<string> GenerateMD5HasList(CancellationToken ct, char prefix)
  2. {
  3. ct.ThrowIfCancellationRequested();
  4. var sw = Stopwatch.StartNew();
  5. var list = new List<string>();
  6. for (int i = ; i < NUM_AES_KEYS; i++)
  7. {
  8. var md5M = MD5.Create();
  9. byte[] data = Encoding.Unicode.GetBytes(Environment.UserName + i);
  10. byte[] result = md5M.ComputeHash(data);
  11. string hexString = ConverToHexString(result);
  12. if (hexString[] == prefix)
  13. {
  14. list.Add(hexString);
  15. }
  16. ct.ThrowIfCancellationRequested();
  17. }
  18. Console.WriteLine("MD5:" + sw.Elapsed);
  19. return list;
  20. }
  1. Console.WriteLine("任务开始...");
  2. var cts = new CancellationTokenSource();
  3. var ct = cts.Token;
  4.  
  5. var t1 = Task.Factory.StartNew(() => GenerateMD5HasList(ct,'A'), ct);
  6. //等待执行完成
  7. t1.Wait();
  8.  
  9. var res = t1.Result;
  10. for (int i = ; i < res.Count; i++)
  11. {
  12. Console.WriteLine(res[i]);
  13. }

而这时的StartNew创建的类型是Task<List<String>>.StartNew源码如下:

  1. public Task<TResult> StartNew<TResult>(Func<TResult> function)
  2. {
  3. StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
  4. Task currTask = Task.InternalCurrent;
  5. return Task<TResult>.StartNew(currTask, function, m_defaultCancellationToken,
  6. m_defaultCreationOptions, InternalTaskOptions.None, GetDefaultScheduler(currTask), ref stackMark);
  7. }

我们还可以将任务串联起来。比如上面的代码。避免写太多代码来检查前面一个任务是否完成。而ContinueWith这个方法可以用来串联多个任务。

  1. var t1 = Task.Factory.StartNew(() => GenerateMD5HasList(ct,'A'), ct);
  2. var t2 = t1.ContinueWith((t) =>
  3. {
  4. for (int i = ; i < t.Result.Count; i++)
  5. {
  6. Console.WriteLine(t.Result[i]);
  7. }
  8. });
  9. //可以等待t2执行完成
  10. t2.Wait();

如果需要设置继续的条件,就要用到TaskContinuationOptions,它是一个枚举类型,用来控制另一个任务执行和调度的可选行为

  1. var t2 = t1.ContinueWith((t) => OtherMethod(t), TaskContinuationOptions.NotOnCanceled);

NotOnCanceled,就是表示上个任务不取消的情况下执行。例如还有NotOnFaulted.如果上一个任务抛出了异常,那么就不会执行。这里就不一一例举了。

小结:这一章主要是将了基于任务的编程模型,学习了任务的创建、状态,以及如何取消、捕获异常和获得返回值,并能串行任务,任务的延续不仅能简化代码,而且还能帮助调度器对很快就要执行的任务采取正确的操作。下一章学习并发集合。

阅读书籍:《C#并行编程高级教程》 链接: 下载链: http://pan.baidu.com/s/1bn1BdBx  密码: fn2d

喜欢看书,也喜欢分享书籍(不限技术书籍)的朋友,诚邀加入书山有路群q:452450927 。

第三期书山有路,大家正在读《女人的起源》。 链接: http://pan.baidu.com/s/1ntEhMHz 密码: 84d8

在喜欢你的人那里,去热爱生活;在不喜欢你的人那里,去看清世界。

【读书笔记】.Net并行编程高级教程(二)-- 任务并行的更多相关文章

  1. 【读书笔记】.Net并行编程高级教程--Parallel

    一直觉得自己对并发了解不够深入,特别是看了<代码整洁之道>觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准.而且在<失控>这本书中也多次提到并发,不管是计算机 ...

  2. Net并行编程高级教程--Parallel

    Net并行编程高级教程--Parallel 一直觉得自己对并发了解不够深入,特别是看了<代码整洁之道>觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准.而且在<失控 ...

  3. 详细的.Net并行编程高级教程--Parallel

    一直觉得自己对并发了解不够深入,特别是看了<代码整洁之道>觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准.而且在<失控>这本书中也多次提到并发,不管是计算机 ...

  4. 《C#并行编程高级教程》第9章 异步编程模型 笔记

    这个章节我个人感觉意义不大,使用现有的APM(异步编程模型)和EAP(基于时间的异步模型)就很够用了,针对WPF和WinForm其实还有一些专门用于UI更新的类. 但是出于完整性,还是将一下怎么使用. ...

  5. 《C#并行编程高级教程》第6章 PLINQ:声明式数据并行 笔记

    PLINQ这个话题好多书都写到过,这本也没有什么特别好的地方. 几个有用和有趣的点记录一下.   顺序的不确定性 用PLINQ就一定要记住并行后会导致顺序不确定的问题.解决方案就是AsOrdered或 ...

  6. 《C#并行编程高级教程》第5章 协调数据结构 笔记

    本章介绍了一些轻量级的同步原语,其中有很大部分是.NET Framework 4才引入的. System.Threading.Barrier 用于一段程序分成多个阶段,每个阶段的开始都需要之前的阶段完 ...

  7. 《C#并行编程高级教程》第2章 命令式编程 笔记

    Parallel.Invoke 并行执行多个方法,只有在所有方法都执行后才会返回 static void Main(string[] args){    Parallel.Invoke(    () ...

  8. .Net并行编程高级教程(二)-- 任务并行

    前面一篇提到例子都是数据并行,但这并不是并行化的唯一形式,在.Net4之前,必须要创建多个线程或者线程池来利用多核技术.现在只需要使用新的Task实例就可以通过更简单的代码解决命令式任务并行问题. 1 ...

  9. 《C#并行编程高级教程》第8章 线程池 笔记

    主要的几个概念(详细最好还是看书,配合插图看)   任务是会被分配到线程上的,而这些线程都在线程池引擎下管理 线程池引擎管理着合适数量的线程池,线程从全局队列获取工作项执行. .NET4 Framew ...

随机推荐

  1. Ext开场表单布局设计

    var form = new Ext.form.FormPanel({ labelAlign: 'right', labelWidth: 60, buttonAlign: 'center', titl ...

  2. android 应用笔记

    android 应用笔记 android 应用笔记 小书匠 Android 综合教程 Android常用技巧 安卓系统架构 安卓源码开发 安卓驱动 Linux内核 安卓应用开发 Java 教程 tic ...

  3. SaaS模式给用户带来的优势

    这两年SaaS服务在中国越来越受欢迎,企业正在从使用本地化软件向SaaS服务转变.由于企业用户人力成本的上升.移动终端设备的兴起以及共享经济对企业的影响,企业采用经营设备.软件的方式也在逐渐发生着变化 ...

  4. mybatis框架中动态SQL的编写

    1.动态SQL:在SQL语句中加入流程控制.比如加入if,foreach等. 重点掌握if语句: 案例1: <update id="updateItem" parameter ...

  5. Jquery插件开发精品教程

    最开始接触jquery对他提供的各种插件总是十分有兴趣,但是总是不理解为什么这样写,从网络上查询了很久终于找到这篇文章,讲解的很详细,分享给大家. 要说jQuery 最成功的地方,我认为是它的可扩展性 ...

  6. 【OpenGL】如何绘制Shadow

    背景 Shadow即阴影,它是光线被不透明物体遮挡而产生的黑暗区域,与光源的方向相反. 在Blender中编辑过程中没有Shadow,只有在经过渲染后才能显示.目前有一个基于Blender的项目,要求 ...

  7. 最小生成树算法——prim算法

    prim算法:从某一点开始,去遍历相邻的边,然后将权值最短的边加入集合,同时将新加入边集中的新点遍历相邻的边更新边值集合(边值集合用来找出新的最小权值边),注意每次更新都需将cost数组中的点对应的权 ...

  8. Angular常用功能

    1.默认选择让第0个元素的class为active ng-class="{active:$index == 0}" 2.指令的例子 <!DOCTYPE html> &l ...

  9. 通过navigationController跳转界面时隐藏navigationBar上的元素

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...

  10. [UE4]AnimDynamics简介

    AnimDynamics简介 Author:Jia Zhipeng AnimDynamics是UE4.11 Preview 5测试版本发布的AnimationBlueprint中的新节点.功能是通过简 ...