.NET Framework4.0 下的多线程
一、简介
在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 下的多线程的更多相关文章
- SharpDevelope 在 Windows 7 SP1 with .net framework4.0 下编译时找不到resgen.exe 解决办法
如果在vs下编译正常,在SharpDevelope下编译报这个错误,可以更改编译时的.netframework版本和C#版本.在 Tool->Project Upgrade 进行项目转换后,一般 ...
- 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 ...
- framework4.0 IIS7下urlrewriter设置问题
framework4.0 IIS7下urlrewriter设置问题 http://www.cnblogs.com/litian/articles/alex.html IIS开启伪静态后html静态页面 ...
- Windows Server2008 下用于.NET Framework3.0版本的问题无法在IIS7中配置.NET Framework4.0节点的问题
Windows Server 2008中,功能列表安装的为.NET Framework3.0. 试了N种方法未升级为.NET Framework4.0(哪位如果可以直接升级为4.0或3.5希望能够分享 ...
- 怎么解决xp系统不能安装NET Framework4.0?
第一步: 如果是XP系统: 1.开始——运行——输入cmd——回车——在打开的窗口中输入net stop WuAuServ 2.开始——运行——输入%windir% 3.在打开的窗口中有个文件夹叫So ...
- Entity Framework4.0 (一)概述(EF4 的Database First方法)
转自:http://www.cnblogs.com/marksun/archive/2011/12/15/2289582.html Entity Framework4.0(以后简称:EF4),是Mic ...
- 【转】 Linux下的多线程编程
作者:gnuhpc 出处:http://www.cnblogs.com/gnuhpc/原文链接:http://www.cnblogs.com/gnuhpc/archive/2012/12/07/280 ...
- Linux下的多线程编程
1 引言 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传统的 Unix也支持线程的概念,但是在一个进程(proces ...
- 多线程编程之Linux环境下的多线程(一)
一.Linux环境下的线程 相对于其他操作系统,Linux系统内核只提供了轻量级进程的支持,并未实现线程模型.Linux是一种“多进程单线程”的操作系统,Linux本身只有进程的概念,而其所谓的“线程 ...
随机推荐
- android 如何设置背景的透明度
半透明<Button android:background="#e0000000" ... />透明<Button android:background=&quo ...
- 二叉树之AVL树的平衡实现(递归与非递归)
这篇文章用来复习AVL的平衡操作,分别会介绍其旋转操作的递归与非递归实现,但是最终带有插入示例的版本会以递归呈现. 下面这张图绘制了需要旋转操作的8种情况.(我要给做这张图的兄弟一个赞)后面会给出这八 ...
- visio2007无法拖动
连按两下键盘上的 “Esc” 键
- Java Annotation 机制源码分析与使用
1 Annotation 1.1 Annotation 概念及作用 1. 概念 An annotation is a form of metadata, that can be added ...
- dev中如何对combox下拉框设置可消除属性以及ASPxGridView中金额,数量的显示,以及总计、grid中某行值
下拉框属性关键:IncrementalFilteringMode="StartsWith" DropDownStyle="DropDown" ASPxGridV ...
- 使用fiddler2抓取手机发出的请求信息
fiddler2 简介:抓包软件,可以替换服务器js,从而实现本地调试 初始化设置: 1.工具——fiddler选项——常规——允许远程计算机连接(打钩) 2.按下图设置 3.设置连接,如 ...
- Exceeded maximum number of retries. Exceeded max scheduling attempts 3 for instance
Exceeded maximum number of retries. Exceeded max scheduling attempts 3 for instance
- js基础笔记
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF- ...
- leetcode 107
107. Binary Tree Level Order Traversal II Given a binary tree, return the bottom-up level order trav ...
- ASP.NET-----Repeater数据控件的用法总结(转)
一.Repeater控件的用法流程及实例: 1.首先建立一个网站,新建一个网页index.aspx. 2.添加或者建立APP_Data数据文件,然后将用到的数据库文件放到APP_Data文件夹中. 3 ...