由于Framework 4.0和Framework 4.5對(duì)Task類稍微有些不同,此處聲明以下代碼都是基于Framework 4.5
員工經(jīng)過長(zhǎng)期磨合與沉淀,具備了協(xié)作精神,得以通過團(tuán)隊(duì)的力量開發(fā)出優(yōu)質(zhì)的產(chǎn)品。創(chuàng)新互聯(lián)建站堅(jiān)持“專注、創(chuàng)新、易用”的產(chǎn)品理念,因?yàn)椤皩W⑺詫I(yè)、創(chuàng)新互聯(lián)網(wǎng)站所以易用所以簡(jiǎn)單”。公司專注于為企業(yè)提供成都做網(wǎng)站、成都網(wǎng)站制作、微信公眾號(hào)開發(fā)、電商網(wǎng)站開發(fā),小程序定制開發(fā),軟件按需求定制開發(fā)等一站式互聯(lián)網(wǎng)企業(yè)服務(wù)。
Task類和Task
主要區(qū)別在于Task構(gòu)造函數(shù)接受的參數(shù)是Action委托,而Task
- Task(Action)
- Task
(Func )
啟動(dòng)一個(gè)任務(wù)
- static void Main(string[] args)
- {
- Task Task1 = new Task(() => Console.WriteLine("Task1"));
- Task1.Start();
- Console.ReadLine();
- }
通過實(shí)例化一個(gè)Task對(duì)象,然后Start,這種方式中規(guī)中矩,但是實(shí)踐中,通常采用更方便快捷的方式
Task.Run(() => Console.WriteLine("Foo"));
這種方式直接運(yùn)行了Task,不像上面的方法還需要調(diào)用Start();
Task.Run方法是Task類中的靜態(tài)方法,接受的參數(shù)是委托。返回值是為該Task對(duì)象。
Task.Run(Action)
Task.Run
Task構(gòu)造方法還有一個(gè)重載函數(shù)如下:
Task 構(gòu)造函數(shù) (Action, TaskCreationOptions),對(duì)應(yīng)的Task泛型版本也有類似構(gòu)造函數(shù)。TaskCreationOptions參數(shù)指示Task創(chuàng)建和執(zhí)行的可選行為。常用的參數(shù)LongRunning。
默認(rèn)情況下,Task任務(wù)是由線程池線程異步執(zhí)行的。如果是運(yùn)行時(shí)間很長(zhǎng)的操作,使用LongRunning 參數(shù)暗示任務(wù)調(diào)度器,將這個(gè)任務(wù)放在非線程池上運(yùn)行。通常不需要用這個(gè)參數(shù),除非通過性能測(cè)試覺得使用該參數(shù)能有更好的性能,才使用。
任務(wù)等待
默認(rèn)情況下,Task任務(wù)是由線程池線程異步執(zhí)行。要知道Task任務(wù)的是否完成,可以通過task.IsCompleted屬性獲得,也可以使用task.Wait來等待Task完成。Wait會(huì)阻塞當(dāng)前線程。
- static void Main(string[] args)
- {
- Task Task1=Task.Run(() => { Thread.Sleep(5000);
- Console.WriteLine("Foo");
- Thread.Sleep(5000);
- });
- Console.WriteLine(Task1.IsCompleted);
- Task1.Wait();//阻塞當(dāng)前線程
- Console.WriteLine(Task1.IsCompleted);
- }
Wait方法有個(gè)重構(gòu)方法,簽名如下:public bool Wait(int millisecondsTimeout),接受一個(gè)時(shí)間。如果在設(shè)定時(shí)間內(nèi)完成就返回true,否則返回false。如下的代碼:
- static void Main(string[] args)
- {
- Task Task1=Task.Run(() => { Thread.Sleep(5000);
- Console.WriteLine("Foo");
- Thread.Sleep(5000);
- });
- Console.WriteLine("Task1.IsCompleted:{0}",Task1.IsCompleted);
- bool b=Task1.Wait(2000);
- Console.WriteLine("Task1.IsCompleted:{0}", Task1.IsCompleted);
- Console.WriteLine(b);
- Thread.Sleep(9000);
- Console.WriteLine("Task1.IsCompleted:{0}", Task1.IsCompleted);
- }
運(yùn)行結(jié)果為:
獲得返回值
要獲得返回值,就要用到Task的泛型版本了。
- static void Main(string[] args)
- {
- Task
Task1 = Task.Run (() => { Thread.Sleep(5000); return Enumerable.Range(1, 100).Sum(); }); - Console.WriteLine("Task1.IsCompleted:{0}",Task1.IsCompleted);
- Console.WriteLine("Task1.IsCompleted:{0}", Task1.Result);//如果方法未完成,則會(huì)等待直到計(jì)算完成,得到返回值才運(yùn)行下去。
- Console.WriteLine("Task1.IsCompleted:{0}", Task1.IsCompleted);
- }
結(jié)果如下:
異常拋出
和線程不同,Task中拋出的異??梢圆东@,但是也不是直接捕獲,而是由調(diào)用Wait()方法或者訪問Result屬性的時(shí)候,由他們獲得異常,將這個(gè)異常包裝成AggregateException類型,再重新拋出捕獲。
- static void Main(string[] args)
- {
- try
- {
- Task
Task1 = Task.Run (() => { throw new Exception("xxxxxx"); return 1; }); - Task1.Wait();
- }
- catch (Exception ex)//error的類型為System.AggregateException
- {
- Console.WriteLine(ex.StackTrace);
- Console.WriteLine("-----------------");
- Console.WriteLine(ex.InnerException.StackTrace);
- }
- }
如上代碼,運(yùn)行結(jié)果如下:
可以看到異常真正發(fā)生的地方。
對(duì)于某些匿名的Task(通過 Task.Run方法生成的,不調(diào)用wait,也不關(guān)心是否運(yùn)行完成),某些情況下,記錄它們的異常錯(cuò)誤也是有必要的。這些異常稱作未觀察到的異常(unobserved exceptions)??梢酝ㄟ^訂閱一個(gè)全局的靜態(tài)事件TaskScheduler.UnobservedTaskException來處理這些異常。只要當(dāng)一個(gè)Task有異常,并且在被垃圾回收的時(shí)候,才會(huì)觸發(fā)這一個(gè)事件。如果Task還處于被引用狀態(tài),或者只要GC不回收這個(gè)Task,這個(gè)UnobservedTaskException事件就不會(huì)被觸發(fā)
例子:
- static void Main(string[] args)
- {
- TaskScheduler.UnobservedTaskException += UnobservedTaskException;
- Task.Run
(() => { throw new Exception("xxxxxx"); return 1; }); - Thread.Sleep(1000);
- }
- static void UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
- {
- Console.WriteLine(e.Exception.Message);
- Console.WriteLine(e.Exception.InnerException.Message);
- }
這樣的代碼直到程序運(yùn)行完成也為未能觸發(fā)UnobservedTaskException,因?yàn)镚C沒有開始做垃圾回收。
在代碼中加入 GC.Collect();
- static void Main(string[] args)
- {
- TaskScheduler.UnobservedTaskException += UnobservedTaskException;
- Task.Run
(() => { throw new Exception("xxxxxx"); return 1; }); - Thread.Sleep(1000);
- GC.Collect();
- GC.WaitForPendingFinalizers();
- }
運(yùn)行后得到如下:
延續(xù)任務(wù)
延續(xù)任務(wù)就是說當(dāng)一個(gè)Task完成后,繼續(xù)運(yùn)行下一個(gè)任務(wù)。通常有2種方法實(shí)現(xiàn)。
一種是使用GetAwaiter方法。GetAwaiter方法返回一個(gè)TaskAwaiter結(jié)構(gòu),該結(jié)構(gòu)有一個(gè)OnCompleted事件,只需對(duì)OnCompleted事件賦值,即可在完成后調(diào)用該事件。
- static void Main(string[] args)
- {
- Task
Task1 = Task.Run (() => { return Enumerable.Range(1, 100).Sum(); }); - var awaiter = Task1.GetAwaiter();
- awaiter.OnCompleted(() =>
- {
- Console.WriteLine("Task1 finished");
- int result = awaiter.GetResult();
- Console.WriteLine(result); // Writes result
- });
- Thread.Sleep(1000);
- }
運(yùn)行結(jié)果如下:
此處調(diào)用GetResult()的好處在于,一旦先前的Task有異常,就會(huì)拋出該異常。而且該異常和之前演示的異常不同,它不需要經(jīng)過AggregateException再包裝了。
另一種延續(xù)任務(wù)的方法是調(diào)用ContinueWith方法。ContinueWith返回的任然是一個(gè)Task類型。ContinueWith方法有很多重載,算上泛型版本,差不多40個(gè)左右的。其中最常用的,就是接受一個(gè)Action或者Func委托,而且,這些委托的第一個(gè)傳入?yún)?shù)都是Task類型,即可以訪問先前的Task對(duì)象。示例:
- static void Main(string[] args)
- {
- Task
Task1 = Task.Run (() => {return Enumerable.Range(1, 100).Sum(); }); - Task1.ContinueWith(antecedent => {
- Console.WriteLine(antecedent.Result);
- Console.WriteLine("Runing Continue Task");
- });
- Thread.Sleep(1000);
- }
使用這種ContinueWith方法和GetAwaiter都能實(shí)現(xiàn)相同的效果,有點(diǎn)小區(qū)別就是ContinueWith如果獲取Result的時(shí)候有異常,拋出的異常類型是經(jīng)過AggregateException包裹的,而GetAwaiter()后的OnCompleted所調(diào)用的方法中,如果出錯(cuò),直接拋出異常。
生成Task的另一種方法,TaskCompletionSource
使用TaskCompletionSource很簡(jiǎn)單,只需要實(shí)例化它即可。TaskCompletionSource有一個(gè)Task屬性,你可以對(duì)該屬性暴露的task做操作,比如讓它wait或者ContinueWith等操作。當(dāng)然,這個(gè)task由TaskCompletionSource完全控制。TaskCompletionSource
- public class TaskCompletionSource
- {
- public void SetResult (TResult result);
- public void SetException (Exception exception);
- public void SetCanceled();
- public bool TrySetResult (TResult result);
- public bool TrySetException (Exception exception);
- public bool TrySetCanceled();
- ...
- }
調(diào)用以上方法意味著對(duì)Task做狀態(tài)的改變,將狀態(tài)設(shè)成completed,faulted或者 canceled。這些方法只能調(diào)用一次,不然會(huì)有異常。Try的方法可以調(diào)多次,只不過返回false而已。
通過一些技巧性的編碼,將線程和Task協(xié)調(diào)起來,通過Task獲得線程運(yùn)行的結(jié)果。
示例代碼:
- static void Main(string[] args)
- {
- var tcs = new TaskCompletionSource
(); - new Thread(() => {
- Thread.Sleep(5000);
- int i = Enumerable.Range(1, 100).Sum();
- tcs.SetResult(i); }).Start();//線程把運(yùn)行計(jì)算結(jié)果,設(shè)為tcs的Result。
- Task
task = tcs.Task; - Console.WriteLine(task.Result); //此處會(huì)阻塞,直到匿名線程調(diào)用tcs.SetResult(i)完畢
- }
說明一下以上代碼:
tcs是TaskCompletionSource
獲得tcs的Task屬性,讀取并打印該屬性的值。那么 Console.WriteLine(task.Result);其實(shí)是會(huì)阻塞的,直到task的result被賦值之后,才會(huì)取消阻塞。而對(duì)task.result的賦值正在一個(gè)匿名線程中做的。也就是說,一直等到匿名線程運(yùn)行結(jié)束,把運(yùn)行結(jié)果賦值給tcs后,task.Result的值才會(huì)被獲得。這正是變相的實(shí)現(xiàn)了線程同步的功能,并且可以獲得線程的運(yùn)行值。而此時(shí)的線程并不是運(yùn)行在線程池上的。
我們可以定義一個(gè)泛型方法,來實(shí)現(xiàn)一個(gè)Task對(duì)象,并且運(yùn)行Task的線程不是線程池線程:
- Task
Run (Func function) - {
- var tcs = new TaskCompletionSource
(); - Thread t = new Thread(() =>
- {
- try { tcs.SetResult(function()); }
- catch (Exception ex) { tcs.SetException(ex); }
- });
- t.IsBackground = true;
- t.Start();//啟動(dòng)線程
- return tcs.Task;
- }
比如什么一個(gè)泛型方法,接受的參數(shù)是Func委托,返回的是Task類型。
該方法中啟動(dòng)一個(gè)線程t,把t設(shè)為后臺(tái)線程,該線程運(yùn)行的內(nèi)容就是傳入的Func委托,并將Func委托的運(yùn)行后的返回值通過tcs.SetResult賦給某個(gè)task。同時(shí),如果有異常的話,就把異常賦給,某個(gè)task,然后將這個(gè)task返回。這樣,直到線程運(yùn)行完畢,才能得到task.Result的值。調(diào)用的時(shí)候:
- Task
task = Run(() => { Thread.Sleep(5000); return Enumerable.Range(1, 100).Sum(); }); - Console.Write(task.Result);//這句會(huì)阻塞當(dāng)前線程,直到task的result值被賦值才行。
TaskCompletionSource的另一個(gè)強(qiáng)大用處,是可以創(chuàng)建Task,而不綁定任何線程,比如,我們可以通過TaskCompletionSource實(shí)現(xiàn)對(duì)某一個(gè)方法的延遲調(diào)用。
代碼示例:
- static Task
delayFunc() - {
- var tcs = new TaskCompletionSource
(); - var timer = new System.Timers.Timer(5000) { AutoReset = false };
- timer.Elapsed += (sender, e) => {
- timer.Dispose();
- int i = Enumerable.Range(1, 100).Sum();
- tcs.SetResult(i);
- };
- timer.Start();
- return tcs.Task;
- }
說明:
delayFunc()方法使用了一個(gè)定時(shí)器,5秒后,定時(shí)器事件觸發(fā),將i的值賦給某個(gè)task的result。返回的是tcs.Task屬性,調(diào)用方式:
- var task = delayFunc();
- Console.Write(task.Result);
task變量得到賦值后,要讀取Result值,必須等到tcs.SetResult(i);運(yùn)行完成才行。這就相當(dāng)于實(shí)現(xiàn)了延遲某個(gè)方法。
當(dāng)然Task自身提供了Delay方法,使用方法如下:
- Task.Delay (5000).GetAwaiter().OnCompleted (() => Console.WriteLine (42));
- 或者:
- Task.Delay (5000).ContinueWith (ant => Console.WriteLine (42));
Delay方法是相當(dāng)于異步的Thread.Sleep();
---------------------------------
參考資料:《C# 5.0 IN A NUTSHELL》
MSDN官方資料