一、简介

  在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. 利用UDP19端口实施DOS攻击的真实案例

    昨天在一个用户现场发现了一个利用UDP19端口对互联网受害者主机进行DOS攻击的真实案例.这个情况是我第一次见到,个人认为对以后遇到此类情况的兄弟具有参考价值.有必要做一个简单的分析记录. 在此次的分 ...

  2. Haar特征

    转自:http://blog.csdn.net/carson2005/article/details/8094699 Haar-like特征,即很多人常说的Haar特征,是计算机视觉领域一种常用的特征 ...

  3. 008 The Generics In JAVA

    泛型是JAVA的核心特型之一,我们先看一个例子: 没有使用泛型前,如下: import java.util.ArrayList; import java.util.List; public class ...

  4. 刻意练习,逃离舒适区——怎么样成为一个高手[罗辑思维]No.183_知识笔记

    2016/10/30 14:31:32   一.对事物的见解分为两类:         1.评论性的见解               说的内容都是对的,符合常理的,但是却是不解决问题的.       ...

  5. 业务gis 搭建一个skyline 的js模板 (一)

    刚刚我们说的是二维的系统,如果要展示三维,我们是不是也需要这样,答案是必须的,是一定要,如果你是基于skyline做三维开发,业务开发人员要去搞那套api估计要吐血,所以我们必须得封装起来,这里不介绍 ...

  6. KVM虚拟化(一)—— 介绍与简单使用

    一.架构及介绍 KVM(Kernel-based Virtual Machine)它由 Quramnet 开发,该公司于 2008年被 Red Hat 收购: 自Linux 2.6.20后整合到内核, ...

  7. 注销CA登录

    //移除CA缓存HttpCookie ticketCookie = Request.Cookies[FormsAuthentication.FormsCookieName];FormsAuthenti ...

  8. oracle通过DBlink连接mysql(MariaDB)

    1.安装先装 mysql-connector-odbc(或 mariadb-connector-odbc )和unixODBChttps://downloads.mariadb.org/mariadb ...

  9. DownLoadFile - FileHandler

    C# 跳转新页面 判断URL文件是不是在于在. C# 指定物理目录下载文件,Response.End导致“正在中止线程”异常的问题 public class FileHandler { public ...

  10. MySQL学习笔记(二)

    二.SQL基本知识 SQL 是一种典型的非过程化程序设计语言,这种语言的特点是:只指定哪些数据被操纵,至于对这些数据要执行哪些操作,以及这些操作是如何执行的,则未被指定.非过程化程序设计语言的优点在于 ...