一、简介

  在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的值,并返回。

  

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Text; namespace TaskTest
{
class Program
{
static void Main(string[] args)
{
// 定义并启动一个线程,计算5乘以3,并返回一个int类型的值
Task<int> task = Task.Factory.StartNew<int>(
() => { return * ; });
// 线程启动并开始执行 foreach (char busySymbol in Utility.BusySymbols())
{
if (task.IsCompleted)
{
Console.Write('\b');
break;
}
Console.Write(busySymbol);
}
Console.WriteLine(); Console.WriteLine(task.Result.ToString());
// 如果执行至此仍未完成那个线程,则输出堆栈信息
System.Diagnostics.Trace.Assert(task.IsCompleted);
}
}
public class Utility
{
public static IEnumerable<char> BusySymbols()
{
string busySymbols = @"-\|/-\|/";
int next = ;
{
while (true)
{
yield return busySymbols[next];
next = (++next) % busySymbols.Length;
yield return '\b';
}
}
}
}
}

输出结果如下两种:

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

Console.Write(busySymbol);

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

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

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

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

四、ContinueWith

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

用法如下:

 static void Main(string[] args)
{
Task<int> task = Task.Factory.StartNew<int>(
() => { return * ; });
Task faultTask = task.ContinueWith(
(antecedentTask) => {
System.Diagnostics.Trace.Assert(task.IsFaulted);
Console.WriteLine("Task State:Faulted");
},TaskContinuationOptions.OnlyOnFaulted);
Task canceledTask = task.ContinueWith(
(antecedentTask) =>
{
System.Diagnostics.Trace.Assert(task.IsCanceled);
Console.WriteLine("Task State:Canceled");
},TaskContinuationOptions.OnlyOnCanceled);
Task completedTask = task.ContinueWith(
(antecedentTask) =>
{
System.Diagnostics.Trace.Assert(task.IsCompleted);
Console.WriteLine("Task State:Complete,Value is "+antecedentTask.Result.ToString());
}, TaskContinuationOptions.OnlyOnRanToCompletion);
completedTask.Wait();
}

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(),才会重新引发线程执行期间未处理的异常,下面的代码展示了这样的机制:

 static void Main(string[] args)
{
Task task = Task.Factory.StartNew( () =>
{
throw new ApplicationException();
Console.WriteLine("我之前有异常");
}); // 显式抛出一个异常
try
{
task.Wait();
}
catch (AggregateException ex)
{
foreach (Exception e in ex.InnerExceptions)
{
Console.WriteLine(e.Message);
}
}
}

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

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

 #region ContinueWith触发先驱任务的异常

             // 获取先驱任务是否失败的标志
bool parentTaskFaulted = false;
Task task = Task.Factory.StartNew(() =>
{
throw new ApplicationException();
});
Task faultedTask = task.ContinueWith( (parentTask) =>
{
parentTaskFaulted = parentTask.IsFaulted;
});
faultedTask.Wait(); // 如果 parentTaskFaulted = false,输出堆栈,即程序正常执行时显示
Trace.Assert(parentTaskFaulted); // task无异常的情况下
if (!task.IsFaulted)
{
task.Wait();
}
else
{
Console.WriteLine("ERROR" + task.Exception.Message);
}
#endregion

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

六、取消任务

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

代码如下:

   static void Main(string[] args)
{
string start = "*".PadRight(Console.WindowWidth - , '*');
Console.WriteLine("Push ENTER to exit."); CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); // 附加了一个token参数,是否取消的标志
Task task = Task.Factory.StartNew(
() => WriteChar(cancellationTokenSource.Token), cancellationTokenSource.Token);
// 等待输入任何一个字符
Console.ReadLine();
// 请求取消
cancellationTokenSource.Cancel();
Console.WriteLine(start);
task.Wait();
Console.ReadLine();
}
private static void WriteChar(CancellationToken cancellationToken)
{
int i = ;
string charChain = string.Empty;
// 无取消请求的时候
while (!cancellationToken.IsCancellationRequested || i == int.MaxValue)
{
charChain += "tom"+i.ToString() + "\n";
Console.WriteLine(charChain);
}
}

上述代码中,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. nfs挂在内核出错 T T *** ERROR: Cannot umount

    今天在U-boot挂载nfs内核是出现如下错误,网上查了解决方案. SOCFPGA_CYCLONE5 # nfs 20000 192.168.0.75:/work/nfs_root/uImageWai ...

  2. java环境变量完整版

    jdk默认安装 Key: JAVA_HOME(新建) Value: C:\Program Files\Java\jdk1.8.0_25 Key: Path(编辑) Value: %JAVA_HOME% ...

  3. Filter Blue Light for Better Sleep(APP 推荐)

    Filter Blue Light for Better Sleep By Carolyn Mohr11 May, 2016 Many people like to use their phones ...

  4. VIM安装 NERDTREE,SOLARIZED 插件

    实验系统版本及VIM版本 系统:CentOS6.3 VIM:7.2 插件介绍 (附上一张效果图) nerdtree(在窗口左侧用于浏览目录结构的插件) solarized(一个自己认为比较护眼的vim ...

  5. Centos7 Openstack - (第一节)基本环境配置

    Centos7 install Openstack - (第一节)基本环境配置 我的blog地址:http://www.cnblogs.com/caoguo 根据openstack官方文档配置 官方文 ...

  6. 【教程】【FLEX】#003 自定义事件、模块间通讯

    本篇笔记,主要阐明 事件是如何创建 和 如何使用自定义事件达到模块之间通讯 的效果. 句子解释: 什么叫做模块之间的通讯呢?? 简单点说,就是两个模块之间可以互相传数据. A模块 可以接收到 B模块的 ...

  7. makefile 学习(一)

    一.Makefile的基本规则 GNU make 规则: target ... : prerequisites ...     command     ....     .... target - 目 ...

  8. 非常不错的IT进阶站点

    1:CSDN http://www.csdn.net/ 2:Iteye http://www.iteye.com 3:拼吾爱 http://pin5i.com 4:月光博客 http://www.wi ...

  9. 解决ScrollView下嵌套ListView、GridView显示不全的问题

    /** * 自定义gridview,解决ListView中嵌套gridview显示不正常的问题(1行半) * @author wangyx * @version 1.0.0 2012-9-14 */ ...

  10. 【Hibernate 6】常用的hql语句以及N+1问题

    HQL:Hibernate Query Language,是Hibernate框架中的查询语言,十分接近于SQL语言!以下介绍一些常用的Hql语句: 一.测试类 Classes类: <span ...