一、简介

  在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. .NET基础操作回顾_使用ADO.NET操作SqlServer使用的类

    有些工具用的久了或者有新工具出现后,就慢慢的遗忘了很多,它们从熟悉的变成陌生,当然,对于我们来说不是好事吧. 今天回顾一下ADO.NET用到的MS的基础类库,先上代码(标准的SqlServer操作) ...

  2. 菜鸟-手把手教你把Acegi应用到实际项目中(6)

    在企业应用中,用户的用户名.密码和角色等信息一般存放在RDBMS(关系数据库)中.前面几节我们采用的是InMemoryDaoImpl,即基于内存的存放方式.这节我们将采用RDBMS存储用户信息. Us ...

  3. 获取Repeater控件中的每一项数据

    var items = rptList.Items;//获取Repeater控件的所有项 foreach (RepeaterItem item in items)//遍历每一项内容 {   var t ...

  4. SparkSQL External Datasource简易使用之AVRO

    下载源码&编译: git clone https://github.com/databricks/spark-avro.git sbt/sbt package Maven GAV: group ...

  5. TestCase--网站登录模块

    以京东的登录页面为例 测试用例设计如下: 一.功能测试 1. 什么都不输入,点击登录按钮,系统是否给出提示2. 输入正确的用户名和密码,点击登录按钮,验证是否能正确登录3. 输入错误的用户名.正确的密 ...

  6. 是否连接VPN

    //需要导入ifadds头文件 //是否连接VPN - (BOOL)isVPNConnected{     struct ifaddrs *interfaces = NULL;     struct ...

  7. NSSet、NSMutableSet

    NSSet和NSArray功能性质一样,用于存储对象,属于集合:只能添加cocoa对象,基本数据类型需要装箱. NSSet . NSMutableSet是无序的集合,在内存中存储方式是不连续的,而NS ...

  8. 洛谷P1467 循环数 Runaround Numbers

    P1467 循环数 Runaround Numbers 89通过 233提交 题目提供者该用户不存在 标签USACO 难度普及/提高- 提交  讨论  题解 最新讨论 暂时没有讨论 题目描述 循环数是 ...

  9. 使用Cookie保存商品浏览记录

    数据流程:页面上是商品列表,点击<a href="productServlet">商品名</a> ==>跳转到自定义的servlet中进行处理,先得到 ...

  10. java学习之(接口)

    使用接口 接口不能用于创建实例,但接口可以用于声明引用类型变量.当使用接口来声明引用类型变量时,这个引用类型变量必须引用到其实现类的对象.除此之外,接口的主要用途就是被实现类实现.归纳起来,接口主要有 ...