async和await出现在C#5.0之后,给并行编程带来了很多便利,尤其是当MVC中的Action也变成async的时候,一切都开始有async的味道了。但是这也为我们的编程埋下了一些隐患。有时可能会出现一些我们不知道如何产生的错误,尤其是如果我们连线程的基础知识都不了解,更不用说如何处理它们了。那么今天我们就来好好看看这两兄弟和他们的叔叔(Task)和爷爷(Thread)的区别和特点。该模式下并行编程的一般介绍包括:启动线程、返回线程结果、线程挂起、线程中的异常处理等创建静态voidMain(){newThread(Go).Start();//Task.Factory.StartNew(Go),从.NET1.0开始就有了;//.NET4.0引入了TPLTask.Run(newAction(Go));//.NET4.5增加了一个Run方法}publicstaticvoidGo(){Console.WriteLine("我是另一个线程");}需要注意的是,创建Thread的实例后,需要手动调用它的Start方法启动它。但是对于Task来说,在StartNew和Run的同时,会创建一个新的线程,并且会立即启动。线程池线程的创建是一件很耗费资源的事情。NET为我们提供了线程池来帮助我们创建和管理线程。Task默认会直接使用线程池,而Thread不会。如果我们不使用Task而想使用线程池,可以使用ThreadPool类。staticvoidMain(){Console.WriteLine("我是主线程:ThreadId{0}",Thread.CurrentThread.ManagedThreadId);ThreadPool.QueueUserWorkItem(Go);控制台.ReadLine();}publicstaticvoidGo(objectdata){Console.WriteLine("我是另一个线程:ThreadId{0}",Thread.CurrentThread.ManagedThreadId);}传入参数staticvoidMain(){newThread(Go).Start("arg1");//在没有匿名委托之前,我们只能这样,一个对象参数newThread(delegate(){//有了匿名之后委托...GoGoGo("arg1","arg2","arg3");});newThread(()=>{//当然还有LambadaGoGoGo("arg1","arg2","arg3");}).Start();Task.Run(()=>{//Task之所以可以如此灵活,是因为Lambda.GoGoGo("arg1","arg2","arg3");});}publicstaticvoidGo(objectname){//TODO}publicstaticvoidGoGoGo(stringarg1,stringarg2,stringarg3){//TODO}返回值Thead不能返回值,但是作为一个更高级的Task,当然要弥补这个功能。staticvoidMain(){//GetDayOfThisWeek在另一个线程中运行vardayName=Task.Run(()=>{returnGetDayOfThisWeek();});Console.WriteLine("今天是:{0}",dayName.Result);}共享数据上面说了参数和返回值,我们来看看线程间共享数据的问题。privatestaticbool_isDone=假;staticvoidMain(){newThread(Done).Start();新线程(完成).开始();}staticvoidDone(){if(!_isDone){_isDone=true;//当第二个线程来的时候,不会再执行(不是绝对的,要看电脑的CPU数量和当时的运行情况)Console.WriteLine("完成");}}线程可以通过静态变量共享数据。线程安全让我们对上面的代码做一个小的调整,来了解什么是线程安全。我们在Done方法中交换了两个句子。privatestaticbool_isDone=false;staticvoidMain(){newThread(Done).Start();新线程(完成).开始();控制台.ReadLine();}staticvoidDone(){if(!_isDone){Console.WriteLine("Done");//猜猜这会执行多少次?_isDone=真;这不会一直发生,但如果你幸运的话,你会中奖的。因为第一个线程还没来得及设置_isDone为true,第二个线程进来了,这不是我们想要的结果。多线程下,结果不是我们预期的结果。安全。锁要解决上面遇到的问题,我们就需要用到锁。锁的类型包括排它锁、互斥锁和读写锁。我们将在这里简单演示排他锁。privatestaticbool_isDone=false;privatestaticobject_lock=newobject();staticvoidMain(){newThread(Done).Start();新线程(完成).开始();控制台.ReadLine();}staticvoidDone(){lock(_lock){if(!_isDone){Console.WriteLine("Done");//猜猜这会被执行多少次?_isDone=真;}}}我们加了锁后,同一时间只允许一个线程访问被锁代码,其他线程会被阻塞。只有释放锁后,其他线程才能执行被锁住的代码。锁定代码。#p#Semaphore这个词我真的不知道怎么翻译。从官方的解释中,我们可以这样理解。它可以控制访问某段代码或某项资源的线程数。超过这个数量后,其他线程就得等待了。现在只有一个线程被释放后,后面的线程才能访问。这与锁的功能类似,只是它不是独占的,它允许一定数量的线程同时访问它。staticSemaphoreSlim_sem=newSemaphoreSlim(3);//我们限制同时访问的线程数为3staticvoidMain(){for(inti=1;i<=5;i++)newThread(Enter).Start(i);控制台.ReadLine();}staticvoidEnter(objectid){Console.WriteLine(id+"开始排队...");_sem.等待();Console.WriteLine(id+"开始执行!");Thread.Sleep(1000*(int)id);Console.WriteLine(id+"执行完毕,离开!");_sem.Release();}一开始,前3行排队后会立即执行,但4和5只会等到有线程退出才能执行。其他线程的异常处理,主线程能不能捕获?publicstaticvoidMain(){try{newThread(Go).Start();}catch(Exceptionex){//其他线程中的异常,我们这里是捕获不到的。Console.WriteLine("异常!");}}staticvoidGo(){thrownull;}升级后的任务呢?publicstaticvoidMain(){try{vartask=Task.Run(()=>{Go();});task.Wait();//调用这句话后,主线程可以捕获task中的异常//对于有返回值的Task,我们不需要在收到它的返回值后调用Wait方法//我们也可以在GetNamevartask2=Task.Run(()=>{returnGetName();})中捕获异常;varname=task2.Result;}catch(Exceptionex){Console.WriteLine("Exception!");}}staticvoidGo(){thrownull;}staticstringGetName(){thrownull;}识别async的小例子&awaitstaticvoidMain(string[]args){Test();//这个方法其实是多余的,可以直接写下面的方法//awaitGetName()//但是因为控制台的入口方法不支持async,所以我们不能在入口方法中使用awaitConsole.WriteLine("CurrentThreadId:{0}",Thread.CurrentThread.ManagedThreadId);}staticasyncTaskTest(){//如果方法被标记为async关键字,可以使用await来调用同样被标记为async的方法//await之后的方法会在另一个线程中执行awaitGetName();}staticasyncTaskGetName(){//delay方法来自.net4.5awaitTask.Delay(1000);//在返回值前面加上async后,方法中可以使用awaitConsole.WriteLine("CurrentThreadId:{0}",Thread.CurrentTh读取.ManagedThreadId);Console.WriteLine("Inantoherthread....");}#p#await原型在await之后的执行顺序感谢locus的修正,await之后不会再开新线程(awaitneverOpenanewthread),所以上图有点问题await不会开一个新建线程,当前线程会一直往下走,直到遇到真正的Async方法(比如HttpClient.GetStringAsync),这个方法内部会使用Task.Run或者Task.Factory.StartNew来启动线程。即如果方法不是.NET提供的Async方法,我们需要在真正创建线程之前自己创建Task。staticvoidMain(string[]args){Console.WriteLine("MainThreadId:{0}\r\n",Thread.CurrentThread.ManagedThreadId);测试();控制台.ReadLine();}staticasyncTaskTest(){Console.WriteLine("BeforecallingGetName,ThreadId:{0}\r\n",Thread.CurrentThread.ManagedThreadId);varname=GetName();//我们这里没有使用await,所以后面的代码可以继续执行//但是如果上面是awaitGetName(),后面的代码不会马上执行,输出会不一样.Console.WriteLine("EndcallingGetName.\r\n");Console.WriteLine("GetresultfromGetName:{0}",awaitname);}staticasyncTaskGetName(){//这还是主线程Console.WriteLine("BeforecallingTask.Run,currentthreadIdis:{0}",Thread.CurrentThread.ManagedThreadId);returnawaitTask.Run(()=>{Thread.Sleep(1000);Console.WriteLine("'GetName'ThreadId:{0}",Thread.CurrentThread.ManagedThreadId);return"Jesse";});}我们再看看那张图: 进入主线程开始调用async方法,返回一个Task。注意此时另一个线程已经开始运行,也就是GetName中的Task已经开始工作了。主线程继续往下走。第三步和第四步同时进行。主线程不会挂掉等待。如果另外一个线程已经执行完毕,name.IsCompleted=true,主线程仍然不用挂掉,直接获取结果即可。如果另一个线程也执行了,name.IsCompleted=false,那么主线程就会挂起,等待结果返回。异步方法只能在调用前添加await吗?staticvoidMain(){测试();控制台.ReadLine();}staticasyncvoidTest(){Tasktask=Task.Run(()=>{Thread.Sleep(5000);return"HelloWorld";});stringstr=awattask;//Console.WriteLine(str)会在5秒后执行到这里;答案很明显:await不是针对async方法的,而是针对async方法返回给我们的Task的,这就是为什么所有的async方法都必须给我们返回一个Task。所以我们也可以在Task前面加上await关键字,其实就是告诉编译器我需要等待Task的返回值或者等待Task执行完毕再继续。如何在不使用await关键字的情况下确认Task执行完成?staticvoidMain(){vartask=Task.Run(()=>{returnGetName();});task.GetAwaiter().OnCompleted(()=>{//2秒后执行varname=task.Result;Console.WriteLine("Mynameis:"+name);});Console.WriteLine("主线程执行完毕");控制台.ReadLine();}staticstringGetName(){Console.WriteLine("Anotherthreadisgettingthename");线程.睡眠(2000);返回“杰西”;}#p#Task.GetAwaiter()和awaitTask的区别?加上await关键字后,后面的代码会被挂起,等待任务执行完毕,有返回值后,再继续往下执行。在这段时间内,主线程会处于挂起状态。GetAwaiter方法将返回一个等待对象(继承INotifyCompletion.OnCompleted方法)。我们只是传入一个delegate,当任务完成时会执行,但不会影响主线程。以下代码将立即执行。这就是为什么我们的结果中的第一句话会是“主线程执行完毕”!Task是如何让主线程挂起等待的?上图右边是主线程没有挂起的情况,和我们的await还是有点区别的,那么在拿到Task的结果之前,如何挂起主线程呢?staticvoidMain(){vartask=Task.Run(()=>{returnGetName();});varname=task.GetAwaiter().GetResult();Console.WriteLine("Mynameis:{0}",name);Console.WriteLine("主线程执行完毕");控制台.ReadLine();}staticstringGetName(){Console.WriteLine("另一个线程正在获取名称");线程.睡眠(2000);返回“杰西”;} Task.GetAwait()方法将返回一个可等待的对象。通过调用该对象的GetResult方法,主线程将被挂起。当然,并非所有案件都会被暂停。还记得我们Task的特点吗?一开始,启动了另一个线程来执行Task。当我们调用它的result时,如果Task已经执行完毕,主线程就可以直接拿到result,不需要等待。如果没有执行完毕,主线程就得挂掉等待。await的本质是调用可等待对象的GetResult方法staticasyncTaskTest(){Tasktask=Task.Run(()=>{Console.WriteLine("另一个线程正在运行!");//这句话只会执行一次Thread.Sleep(2000);return"HelloWorld";});//这里主线程会挂起等待任务执行完我们得到返回结果varresult=task.GetAwaiter().GetResult();//这里不会有pending等待,因为任务已经执行完毕,我们可以直接得到结果varresult2=awaittask;控制台.WriteLine(str);}至此,await的真相大白,欢迎评论。享受编码!:)本文来自:http://www.cnblogs.com/jesse2013/p/async-and-await.html