一、简介

  在4.0之前,多线程只能用Thread或者ThreadPool,而4.0下提供了功能强大的Task处理方式,这样免去了程序员自己维护线程池,而且可以申请取消线程等。。。所以本文主要描述Task的特性。

二、Task的优点

  操作系统自身可以实现线程,并且提供了非托管的API来创建与管理这些线程。但是C#是运行在CLR上面的,为了方便的创建与管理线程,CLR对这些API进行了封装,通过System.Threading.Tasks.Task公开了这些包装。

  在计算机中,创建线程十分耗费珍贵的计算机资源,所以Task启动时,不是直接创建一个线程。而是从线程池请求一个线程。并且通过对线程的抽象,程序员一般和Task打交道就好,这样降低了高效管理多线程的复杂度。

三、Task使用示例。

  Task可以获取一个返回值,下面的程序实现如下功能:利用Task启动一个新的线程,然后计算3*5的值,并返回。

  

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using System.Text;
  6.  
  7. namespace TaskTest
  8. {
  9. class Program
  10. {
  11. static void Main(string[] args)
  12. {
  13. // 定义并启动一个线程,计算5乘以3,并返回一个int类型的值
  14. Task<int> task = Task.Factory.StartNew<int>(
  15. () => { return * ; });
  16. // 线程启动并开始执行
  17.  
  18. foreach (char busySymbol in Utility.BusySymbols())
  19. {
  20. if (task.IsCompleted)
  21. {
  22. Console.Write('\b');
  23. break;
  24. }
  25. Console.Write(busySymbol);
  26. }
  27. Console.WriteLine();
  28.  
  29. Console.WriteLine(task.Result.ToString());
  30. // 如果执行至此仍未完成那个线程,则输出堆栈信息
  31. System.Diagnostics.Trace.Assert(task.IsCompleted);
  32. }
  33. }
  34. public class Utility
  35. {
  36. public static IEnumerable<char> BusySymbols()
  37. {
  38. string busySymbols = @"-\|/-\|/";
  39. int next = ;
  40. {
  41. while (true)
  42. {
  43. yield return busySymbols[next];
  44. next = (++next) % busySymbols.Length;
  45. yield return '\b';
  46. }
  47. }
  48. }
  49. }
  50. }

输出结果如下两种:

可以看出, 第一次运行如左图。首次运行到if (task.IsCompleted)的时候,计算3*5个线程还没有执行完,所以直接执行:

  1. Console.Write(busySymbol);

输出了“-”,第二次到if的时候,3*5计算完成,执行if里面的内容,输出换行,跳出。然后执行到 Console.WriteLine(task.Result.ToString()); 输出15

第二次运行如右图。首次运行到if的时候,3*5已经计算完成,所以只输出了一个空的换行。然后输出15。其中接收返回值的语句是:

  1. Console.WriteLine(task.Result.ToString());

当然,Task还有一套start的方法,但是不常用,用Task的静态Factory属性的StartNes方法就可以实例化并启动一个线程了,而且,附带指定了返回值的类型。

四、ContinueWith

Task包含了一个Continue的方法,这个方法可以将多个任务连接起来,可以指定当前线程完成之后启动哪个或者哪些线程。ContinueWith会返回另外一个Task,所以工作链可以持续下去。

用法如下:

  1. static void Main(string[] args)
  2. {
  3. Task<int> task = Task.Factory.StartNew<int>(
  4. () => { return * ; });
  5. Task faultTask = task.ContinueWith(
  6. (antecedentTask) => {
  7. System.Diagnostics.Trace.Assert(task.IsFaulted);
  8. Console.WriteLine("Task State:Faulted");
  9. },TaskContinuationOptions.OnlyOnFaulted);
  10. Task canceledTask = task.ContinueWith(
  11. (antecedentTask) =>
  12. {
  13. System.Diagnostics.Trace.Assert(task.IsCanceled);
  14. Console.WriteLine("Task State:Canceled");
  15. },TaskContinuationOptions.OnlyOnCanceled);
  16. Task completedTask = task.ContinueWith(
  17. (antecedentTask) =>
  18. {
  19. System.Diagnostics.Trace.Assert(task.IsCompleted);
  20. Console.WriteLine("Task State:Complete,Value is "+antecedentTask.Result.ToString());
  21. }, TaskContinuationOptions.OnlyOnRanToCompletion);
  22. completedTask.Wait();
  23. }

ContinueWith的参数是一个与task(即后面任务的先驱任务的祖先)相同类型的Task参数。当启动后代任务时,自动将先驱任务赋值给ContinueWith的参数,所以本例输出结果是:

Task State:Complete,Value is 15.

如果我不使用completedTask.Wait();这一句,那么主线程完成后,不会去管task及其后续任务是否完成,就退出,所以加上了这几句话,这样避免task与后继任务执行完之前退出。这样就可以将任务连接回调用线程(main)了。当然要注意,这个例子中只有completeTask是存在的,因为task是正常执行的。不能用canceledTask.Wait(); 因为这个任务在task正常的情况下,永远不会被执行。

五、异常处理

当然也是用try-catch捕捉异常,但是在哪何时捕捉异常,都是一个问题。

从CLR2.0开始,在终结器线程、线程池线程和用户自己创建的线程中发生的未处理的异常一般会在异常层次结构中冒泡。如果冒泡到上一层,可以捕捉到这个异常,则十分好。Task支持这样一个机制;

即,Task在执行期间发生了未处理的异常,这个异常会被禁止(suppressed),抑制,线程后面不继续执行,标记为运行完成。直到调用某个任务完成成员例如:Wait(),Result,Task.WaitAll()或者Task.WaitAny(),才会重新引发线程执行期间未处理的异常,下面的代码展示了这样的机制:

  1. static void Main(string[] args)
  2. {
  3. Task task = Task.Factory.StartNew( () =>
  4. {
  5. throw new ApplicationException();
  6. Console.WriteLine("我之前有异常");
  7. }); // 显式抛出一个异常
  8. try
  9. {
  10. task.Wait();
  11. }
  12. catch (AggregateException ex)
  13. {
  14. foreach (Exception e in ex.InnerExceptions)
  15. {
  16. Console.WriteLine(e.Message);
  17. }
  18. }
  19. }

从task线程里面抛出了异常,从wait()的时候捕捉到了异常。注意  catch (AggregateException ex)里面的参数是 AggregateException,这是一个异常集合。

当然,还有另外一种方法来处理这种异常。就是使用前文提到的ContinueWith认为,利用ContinueWith()中的task参数,可以评估先驱任务的Exception属性。代码如下:

  1. #region ContinueWith触发先驱任务的异常
  2.  
  3. // 获取先驱任务是否失败的标志
  4. bool parentTaskFaulted = false;
  5. Task task = Task.Factory.StartNew(() =>
  6. {
  7. throw new ApplicationException();
  8. });
  9. Task faultedTask = task.ContinueWith( (parentTask) =>
  10. {
  11. parentTaskFaulted = parentTask.IsFaulted;
  12. });
  13. faultedTask.Wait();
  14.  
  15. // 如果 parentTaskFaulted = false,输出堆栈,即程序正常执行时显示
  16. Trace.Assert(parentTaskFaulted);
  17.  
  18. // task无异常的情况下
  19. if (!task.IsFaulted)
  20. {
  21. task.Wait();
  22. }
  23. else
  24. {
  25. Console.WriteLine("ERROR" + task.Exception.Message);
  26. }
  27. #endregion

注意,并不是调用task.Wait()引发的异常,而是用faultedTask去检查task是否产生了异常。

六、取消任务

Task中取代了粗暴的kill和absort,而是设置了一个变量,然后主线程可以申请取消子线程,当线程收到取消信号时,会执行完当前的一次,然后取消。

代码如下:

  1. static void Main(string[] args)
  2. {
  3. string start = "*".PadRight(Console.WindowWidth - , '*');
  4. Console.WriteLine("Push ENTER to exit.");
  5.  
  6. CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
  7.  
  8. // 附加了一个token参数,是否取消的标志
  9. Task task = Task.Factory.StartNew(
  10. () => WriteChar(cancellationTokenSource.Token), cancellationTokenSource.Token);
  11. // 等待输入任何一个字符
  12. Console.ReadLine();
  13. // 请求取消
  14. cancellationTokenSource.Cancel();
  15. Console.WriteLine(start);
  16. task.Wait();
  17. Console.ReadLine();
  18. }
  19. private static void WriteChar(CancellationToken cancellationToken)
  20. {
  21. int i = ;
  22. string charChain = string.Empty;
  23. // 无取消请求的时候
  24. while (!cancellationToken.IsCancellationRequested || i == int.MaxValue)
  25. {
  26. charChain += "tom"+i.ToString() + "\n";
  27. Console.WriteLine(charChain);
  28. }
  29. }

上述代码中,cancellationTokenSource.Cancel()与 task.wait();之间打印星号,我们在运行结果中可能会发现,在星号后面仍然输出了一个char,因为cancel后,线程不会马上终止,而是执行完当前的代码,然后直到下次判断是否终止后才终止。

不过这个例子中,WirteChar中的while循环太短暂,所以没有很好的展示出task可能的额外的一次执行。

七、多线程编程中,三种方法都可选的情况下,优先使用Task的方式,其次使用Threadpool,最次之使用Thread

参考资料: 《C#本质论》第三版第18章

.NET Framework4.0 下的多线程的更多相关文章

  1. SharpDevelope 在 Windows 7 SP1 with .net framework4.0 下编译时找不到resgen.exe 解决办法

    如果在vs下编译正常,在SharpDevelope下编译报这个错误,可以更改编译时的.netframework版本和C#版本.在 Tool->Project Upgrade 进行项目转换后,一般 ...

  2. VC++6.0 下配置 pthread库2010年12月12日 星期日 13:14VC下的pthread多线程编程 转载

    VC++6.0 下配置 pthread库2010年12月12日 星期日 13:14VC下的pthread多线程编程     转载 #include <stdio.h>#include &l ...

  3. framework4.0 IIS7下urlrewriter设置问题

    framework4.0 IIS7下urlrewriter设置问题 http://www.cnblogs.com/litian/articles/alex.html IIS开启伪静态后html静态页面 ...

  4. Windows Server2008 下用于.NET Framework3.0版本的问题无法在IIS7中配置.NET Framework4.0节点的问题

    Windows Server 2008中,功能列表安装的为.NET Framework3.0. 试了N种方法未升级为.NET Framework4.0(哪位如果可以直接升级为4.0或3.5希望能够分享 ...

  5. 怎么解决xp系统不能安装NET Framework4.0?

    第一步: 如果是XP系统: 1.开始——运行——输入cmd——回车——在打开的窗口中输入net stop WuAuServ 2.开始——运行——输入%windir% 3.在打开的窗口中有个文件夹叫So ...

  6. Entity Framework4.0 (一)概述(EF4 的Database First方法)

    转自:http://www.cnblogs.com/marksun/archive/2011/12/15/2289582.html Entity Framework4.0(以后简称:EF4),是Mic ...

  7. 【转】 Linux下的多线程编程

    作者:gnuhpc 出处:http://www.cnblogs.com/gnuhpc/原文链接:http://www.cnblogs.com/gnuhpc/archive/2012/12/07/280 ...

  8. Linux下的多线程编程

    1 引言 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传统的 Unix也支持线程的概念,但是在一个进程(proces ...

  9. 多线程编程之Linux环境下的多线程(一)

    一.Linux环境下的线程 相对于其他操作系统,Linux系统内核只提供了轻量级进程的支持,并未实现线程模型.Linux是一种“多进程单线程”的操作系统,Linux本身只有进程的概念,而其所谓的“线程 ...

随机推荐

  1. ThreadLocal意为变量副本

    http://blog.csdn.net/lufeng20/article/details/24314381

  2. VC++中,如何定义callback函数和它的触发事件?

    对于回调函数的编写始终是写特殊处理功能程序时用到的技巧之一.先介绍一下回调的使用基本方法与原理. 1.在这里设:回调函数为A()(这是最简单的情况,不带参数,但我们应用的实际情况常常很会复杂),使用回 ...

  3. matlab 椭圆方程拟合

    拟合椭圆首先要知道各个点的坐标,然和带入如下公式: x = [59 136 58 137 57 137 56 137 55 138 54 139 53 140 52 141 51 142 51 143 ...

  4. Hibernate 常见异常

    Hibernate 常见异常net.sf.hibernate.MappingException        当出现net.sf.hibernate.MappingException: Error r ...

  5. JS实现电子时钟

          目前有个小项目,在首页头部导航栏里需要一个电子时钟的效果,于是我就采用如下代码实现了一个电子时钟的效果.不过不完美,第一种方式容易导致网页莫名其妙的异常,后来觉得可能是做的操作太多了,然后 ...

  6. Oracle之别名小结

    今天在写一个简单的SQL语句并执行时抛出了如下图所示的一个错误提示信息! 恩,此异常信息很明显,在SQL语句中标示符的长度过长了,简短一些就应该没问题了,我查看了一下我的SQL语句发现是我的查询字段的 ...

  7. SQL to_char,to_date日期字符串转换问题

    1.转换函数 与date操作关系最大的就是两个转换函数:to_date(),to_char() to_date() 作用将字符类型按一定格式转化为日期类型: 具体用法:to_date('2004-11 ...

  8. python 基础知识(一)

    python 基础知识(一) 一.python发展介绍 Python的创始人为Guido van Rossum.1989年圣诞节期间,在阿姆斯特丹,Guido为了打发圣诞节的无趣,决心开发一个新的脚本 ...

  9. IE CSS HACK

    IE 属性值 HACK .test { background:blue; /*所有浏览器*/ background:yellow\9; /*所有IE浏览器*/ background:green\0; ...

  10. EntityFramework追踪Sql语句

    方法一:SQL Profile 这个工具只有sql标准版(standard) 及以上版本才有,我装的是SqlServer2012 Express,所以采用方法2. 方法二:EntityFramewor ...