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

  

 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 5 * 3; });
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 = 0;
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个线程还没有执行完,所以直接执行:

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,所以工作链可以持续下去。

用法如下:

 1 static void Main(string[] args)
 2         {
 3             Task<int> task = Task.Factory.StartNew<int>(
 4                 () => { return 3 * 5; });
 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属性。代码如下:

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

六、取消任务

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

代码如下:

 1   static void Main(string[] args)
 2         {
 3 string start = "*".PadRight(Console.WindowWidth - 1, ‘*‘);
 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 = 0;
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

时间: 2024-08-25 12:05:28

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

VC6.0下创建多线程的方法和注意的事项

#include<stdio.h> #include <process.h> #include <stdio.h> #include <windows.h> DWORD _stdcall ThreadProc(LPVOID lpParameter)//线程执行函数 { int si=100; while(si>0) { printf("子线程输出数字:%d\n",si--); Sleep(1000); } return 0; } i

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

VC++6.0 下配置 pthread库2010年12月12日 星期日 13:14VC下的pthread多线程编程     转载 #include <stdio.h>#include <stdlib.h>#include <pthread.h> void* tprocess1(void* args){       int i=1;       while(i<=10){            printf("process1:%d\n",i);

Linux下模拟多线程的并发并发shell脚本

分享一个在Linux下模拟多线程的并发脚本,使用这个脚本可以同时批量在定义数量的服务器上执行相关命令,比起普通for/while循环只能顺序一条一条执行的效率高非常多,在管理大批服务器时非常的实用.     以下脚本功能是通过scp(也可选rsync)向上千台服务器传更新包,脚本运行后同时在后台有50个scp进程向服务器传包.#!/bin/baship=`cat iplist.txt|grep -v "#"|awk '{print $1}'`   #过滤服务器IPdir='/usr/l

linux下的多线程

1 引言 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传统的Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味着多进程.现在,多线程技术已经被许多操作系统所支持,包括Windows/NT,当然,也包括Linux. 为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题. 使用多线程的理由之一是和进程相比,它

IIS注册Framework4.0

打开iis,确认一下framework4.0是否已经安装. 开始->控制面板->管理工具->Internet信息服务->应用程序池(左边栏)->观察右边主界面.net framework版本栏,是否已经安装过了.   打开dos命令窗口,打开窗口时请以管理员的身份打开. 开始->输入cmd->找到cmd.exe->右键cmd.exe->以管理员身份打开(弹出菜单)->打开dos命名窗口.   打开framework4.0所在目录,一般情况下在此目

【转】Linux下的多线程编

作者:gnuhpc 出处:http://www.cnblogs.com/gnuhpc/原文链接:http://www.cnblogs.com/gnuhpc/archive/2012/12/07/2807484.html 本文作者: 姚继锋 (2001-08-11 09:05:00) 黄鹏程(2009-03-13) converse (2009-01-15) 1 引言 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传

【转】Linux下的多线程编程

1 引言 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传统的 Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味着多进程.现在,多线程技术已经被许多操作系统所支持,包括Windows也包括Linux.  为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题.  使用多线程的理由之一是和进程相比,它是一种非

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

第一步: 如果是XP系统: 1.开始——运行——输入cmd——回车——在打开的窗口中输入net stop WuAuServ 2.开始——运行——输入%windir% 3.在打开的窗口中有个文件夹叫SoftwareDistribution,把它重命名为SDold 4.开始——运行——输入cmd——回车——在打开的窗口中输入net start WuAuServ 第二步: 1.开始——运行——输入regedit——回车 2.找到注册表,HKEY_LOCAL_MACHINE\SOFWARE\Micros

C#实现http协议下的多线程文件传输

用C#实现HTTP协议下的多线程文件传输转自  http://developer.51cto.com/art/201105/263066_all.htm C#(C Sharp)是微软(Microsoft)为.NET Framework量身订做的程序语言,C#拥有C/C++的强大功能以及Visual Basic简易使用的特性,是第一个组件导向(Component-oriented)的程序语言,和C++与Java一样亦为对象导向(object-oriented)程序语言.下面主要介绍的是用C#实现H