【转】1.A(译).NET4.X 并行任务中Task.Start()的FAQ
傳送門:異步編程系列目錄……
?
?????????近期有不少人向我咨詢關(guān)于Task的Start()方法。比如:何時使用及何時不使用Start()、Start()又做了些什么……我想在這里回答一些問題試圖澄清和平息任何關(guān)于Start()方法是什么以及做了什么的誤解。
?
1.?????????問題:我什么時候能使用Task的Start()方法?
?????????只有Task處于TaskStatus.Created狀態(tài)時才能使用實(shí)例方法Start()。并且,只有在使用Task的公共構(gòu)造函數(shù)構(gòu)造的Task實(shí)例才能處于TaskStatus.Created狀態(tài)。
表示?Task?的生命周期中的當(dāng)前階段。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public?enum?TaskStatus { ????// 該任務(wù)已初始化,但尚未被計(jì)劃。 ????Created = 0, ????// 該任務(wù)正在等待 .NET Framework 基礎(chǔ)結(jié)構(gòu)在內(nèi)部將其激活并進(jìn)行計(jì)劃。 ????WaitingForActivation = 1, ????// 該任務(wù)已被計(jì)劃執(zhí)行,但尚未開始執(zhí)行。 ????WaitingToRun = 2, ? ????// 該任務(wù)正在運(yùn)行,但尚未完成。 ????Running = 3, ????// 該任務(wù)已完成執(zhí)行,正在隱式等待附加的子任務(wù)完成。 ????WaitingForChildrenToComplete = 4, ? ????// 已成功完成執(zhí)行的任務(wù)。 ????RanToCompletion = 5, ????// 該任務(wù)已通過對其自身的 CancellationToken 引發(fā) OperationCanceledException 異常 ????Canceled = 6, ????// 由于未處理異常的原因而完成的任務(wù)。 ????Faulted = 7, } |
?
2.?????????問題:使用Task.Run()/Task.ContinueWith()/Task.Factory.StartNew()/TaskCompletionSource/異步方法(即使用async與await關(guān)鍵字的方法)……應(yīng)該調(diào)用Start()方法嗎?
不應(yīng)該。不僅不應(yīng)該,而且也不能,因?yàn)榇藭r調(diào)用Start()會報異常。從問題1可知:Start()實(shí)例方法只適用于TaskStatus.Created狀態(tài)的Task。由上面提到的方式創(chuàng)建的Task其狀態(tài)不是TaskStatus.Created,而是如TaskStatus.WaitingForActivation、TaskStatus.Running或TaskStatus.RanToCompletion。
?
3.?????????問題:Start()方法實(shí)際做了什么?
Start()將任務(wù)排隊(duì)到目標(biāo)TaskScheduler(無參的Start()重載任務(wù)調(diào)度者為TaskScheduler.Current)。當(dāng)你使用Task的構(gòu)造函數(shù)創(chuàng)建一個Task實(shí)例時,它處于創(chuàng)建狀態(tài)(TaskStatus.Created),它沒有與任何調(diào)度器關(guān)聯(lián),也沒有真真被執(zhí)行。如果你永遠(yuǎn)不調(diào)用Start()方法,那么此任務(wù)永遠(yuǎn)不會排隊(duì)也不會完成。為了讓任務(wù)被執(zhí)行,它需要在調(diào)度器上進(jìn)行排隊(duì),以便調(diào)度器在合適的時刻執(zhí)行它。在Task上調(diào)用Start()方法將改變?nèi)蝿?wù)內(nèi)部的一些數(shù)據(jù)(eg:狀態(tài)從Created改變?yōu)閃aitingToRun)并且將任務(wù)通過TaskScheduler實(shí)例的QueueTask()方法排隊(duì)到目標(biāo)調(diào)度器。此時,此任務(wù)未來的執(zhí)行掌握在調(diào)度器手中,最終會通過TaskScheduler的TryExecuteTask實(shí)例方法執(zhí)行。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | ?????// 表示一個處理將任務(wù)排隊(duì)到線程中的底層工作的對象。 ?????public?abstract?class?TaskScheduler ?????{ ??????????protected?TaskScheduler(); ?? ??????????// 獲取與當(dāng)前正在執(zhí)行的任務(wù)關(guān)聯(lián)的 TaskScheduler。 ??????????public?static?TaskScheduler Current { get; } ??????????// 獲取由 .NET Framework 提供的默認(rèn) TaskScheduler 實(shí)例。 ??????????public?static?TaskScheduler Default { get; } ??????????// 創(chuàng)建一個與當(dāng)前 SynchronizationContext 關(guān)聯(lián)的 TaskScheduler。 ??????????public?static?TaskScheduler FromCurrentSynchronizationContext(); ? ??????????// 當(dāng)出錯的 Task 的未觀察到的異常將要觸發(fā)異常升級策略時發(fā)生,默認(rèn)情況下,這將終止進(jìn)程。 ??????????public?static?event?EventHandler<UnobservedTaskExceptionEventArgs> UnobservedTaskException; ? ??????????// 獲取此 TaskScheduler 實(shí)例的唯一 ID。 ??????????public?int?Id { get; } ??????????// 指示此 TaskScheduler 能夠支持的最大并發(fā)級別,默認(rèn)計(jì)劃程序返回 System.Int32.MaxValue。 ??????????public?virtual?int?MaximumConcurrencyLevel { get; } ? ??????????// 僅對于調(diào)試器支持,生成當(dāng)前排隊(duì)到計(jì)劃程序中等待執(zhí)行的 Task 實(shí)例的枚舉。 ??????????protected?abstract?IEnumerable<Task> GetScheduledTasks(); ??????????// 將 Task 排隊(duì)到計(jì)劃程序中。 ??????????protected?internal?abstract?void?QueueTask(Task task); ??????????// 嘗試將以前排隊(duì)到此計(jì)劃程序中的 Task 取消排隊(duì)。 ??????????protected?internal?virtual?bool?TryDequeue(Task task); ??????????// 嘗試在此計(jì)劃程序上執(zhí)行提供的 Task。執(zhí)行失敗的常見原因是, ??????????// 該任務(wù)先前已經(jīng)執(zhí)行或者位于正在由另一個線程執(zhí)行的進(jìn)程中。 ??????????protected?bool?TryExecuteTask(Task task); ??????????// 確定提供的 Task是否可以在此調(diào)用中同步執(zhí)行,如果可以,將執(zhí)行該任務(wù)。 ??????????//?? taskWasPreviouslyQueued: ??????????//???? 一個布爾值,該值指示任務(wù)之前是否已排隊(duì)。如果此參數(shù)為 True,則該任務(wù)以前可能已排隊(duì)(已計(jì)劃); ??????????//???? 如果為 False,則已知該任務(wù)尚未排隊(duì),此時將執(zhí)行此調(diào)用,以便以內(nèi)聯(lián)方式執(zhí)行該任務(wù),而不用將其排隊(duì)。 ??????????// 返回結(jié)果: ??????????//???? 一個布爾值,該值指示是否已以內(nèi)聯(lián)方式執(zhí)行該任務(wù)。 ??????????protected?abstract?bool?TryExecuteTaskInline(Task task, bool?taskWasPreviouslyQueued); } |
?
4.?????????問題:我能在同一個Task上調(diào)用多次Start()方法嗎?
不行,Task只能離開創(chuàng)建狀態(tài)(TaskStatus.Created)一次,而Start()方法使Task離開創(chuàng)建狀態(tài)。因此,Start()方法只能使用一次。任何企圖在不是創(chuàng)建狀態(tài)的Task上調(diào)用Start()都將導(dǎo)致一個異常。Start()方法采用同步方式運(yùn)行以確保任務(wù)對象保持一致的狀態(tài),即使是同時調(diào)用多次Start(),也只可能有一個調(diào)用會成功。
?
5.?????????問題:使用Task.Start()與使用Task.Factory.StartNew()有何不同?
Task.Factory.StartNew()可快速創(chuàng)建一個Task并且開啟任務(wù)。代碼如下:
| 1 | var?t = Task.Factory.StartNew(someDelegate); |
這等效于:
| 1 2 | var?t = new?Task(someDelegate); t.Start(); |
表現(xiàn)方面,前者更高效。就像在問題3中提到的,Start()采用同步方式運(yùn)行以確保任務(wù)對象保持一致的狀態(tài)即使是同時調(diào)用多次Start(),也可能只有一個調(diào)用會成功。相比之下,StartNew()知道沒有其他代碼能同時啟動任務(wù),因?yàn)樵赟tartNew()返回之前它不會將創(chuàng)建的Task引用給任何人,所以StartNew()不需要采用同步方式執(zhí)行。
?
6.?????????問題:我聽說Task.Result屬性也能開啟任務(wù),真的嗎?
不能,只有兩種方式可能使Task離開其創(chuàng)建狀態(tài):
1)?????????將一個CancellationToken傳遞給Task的構(gòu)造函數(shù),并且這個token已經(jīng)或稍后請求取消。如果Task任然處于Created/WaitingForActivation/WaitingToRun,當(dāng)token取消時,Task將改變?yōu)門askStatus.Canceled狀態(tài)。
2)?????????在Task上調(diào)用Start()。
因此,?Result屬性不能開啟任務(wù)。如果對處于創(chuàng)建狀態(tài)的Task實(shí)例上調(diào)用Wait()方法或Result屬性,這個調(diào)用將被阻塞,需要等待開啟任務(wù),這樣它就可以排隊(duì)到調(diào)度器,調(diào)度程序最終會執(zhí)行它,然后完成任務(wù)。被阻塞的調(diào)用就被喚醒。
?????????你可能會認(rèn)為不是這樣的。Result能開啟任務(wù),但是這只適用于“內(nèi)聯(lián)”任務(wù)的執(zhí)行。如果一個任務(wù)已經(jīng)排隊(duì)到TaskScheduler,但是這個任務(wù)可能任然保持在調(diào)取器的任務(wù)隊(duì)列中。當(dāng)你對被排隊(duì)的任務(wù)請求Result屬性時,運(yùn)行時將嘗試內(nèi)聯(lián)任務(wù)的執(zhí)行而不是純粹的阻塞和等待調(diào)度器在未來某個時刻使用其他線程來完成任務(wù)執(zhí)行。因此,調(diào)用Result屬性可能終止于TaskScheduler的TryExecuteTaskInline()方法的調(diào)用,并且如何處理請求由TaskScheduler決定。
?
7.?????????問題:我應(yīng)該提供返回未啟動任務(wù)的公共APIs嗎?
更恰當(dāng)?shù)膯栴}是“我應(yīng)該提供處于創(chuàng)建狀態(tài)任務(wù)的公共APIs嗎”,答案是“不能”。
?????????基本原因是:當(dāng)你正常調(diào)用同步方法,該方法將很快被調(diào)用執(zhí)行。對于返回Task的方法,你可以把Task看做是異步方法完成的結(jié)果。但是這并不能改變需要調(diào)用Start()方法開始相關(guān)操作的事實(shí)。因此,返回一個處于創(chuàng)建狀態(tài)的Task的異步方法是很奇怪的,只是想代表一個沒有開始的操作?
?????????因此,如果你有一個返回Task的公共方法,并且Task是使用構(gòu)造函數(shù)創(chuàng)建的,請確保你在返回Task之前開啟任務(wù)。否則,很可能在APIs使用方導(dǎo)致死鎖或類似的問題,因?yàn)槭褂梅狡诖{(diào)用完成時Task也最終完成,但如果返回一個尚未啟動的任務(wù),它將永遠(yuǎn)不會完成。有些框架允許你參數(shù)化方法或委托,返回Task甚至驗(yàn)證返回任務(wù)的狀態(tài),如果Task還是創(chuàng)建狀態(tài)就為其拋出異常。
?
8.?????????問題:我應(yīng)該使用Task的構(gòu)造函數(shù)?+ Task的Start()實(shí)例方法嗎?
在大多數(shù)情況下,你最好使用一些其他機(jī)制。比如,如果你只是想計(jì)劃一個任務(wù)來運(yùn)行你提供的委托,你最好使用Task.Run()靜態(tài)方法或Task.Factory的StartNew()實(shí)例方法,而不是使用Task的構(gòu)造函數(shù)創(chuàng)建一個任務(wù)再調(diào)用Start()開啟它,這不僅僅減少了代碼量,并且更加高效(見問題5回答),另外還可以減少犯錯的可能,比如忘記開啟任務(wù)。
當(dāng)然,在一些情況下使用Task的構(gòu)造函數(shù)+Start()更加有意義。比如,需要根據(jù)某些原因來選擇傳遞而來的Task,然后再使用Start()方法來實(shí)際排隊(duì)任務(wù)。
另外,一個更明顯的示例是,如果你想獲得任務(wù)本身的引用,可能使用了如下代碼:
| 1 2 | Task theTask = null; theTask = Task.Run(() => Console.WriteLine(“My ID is?{0}.”, theTask.Id)); |
?????????有問題,存在競爭?在Task.Run()方法內(nèi)部,會創(chuàng)建一個新Task對象并且將其排隊(duì)到線程池調(diào)度器中。如果線程池比較空閑,那么會立即分配一個輔助線程開始執(zhí)行任務(wù)。新創(chuàng)建的任務(wù)最后會存儲在theTask變量中,輔助線程和調(diào)用Task.Run()的線程會發(fā)生競爭的訪問此變量。我們能解決這種競爭通過分離構(gòu)造函數(shù)與TaskScheduler:
| 1 2 3 | Task theTask = null; theTask = new?Task(() =>Console.WriteLine(“My ID is?{0}.”, theTask.Id)); theTask.Start(TaskScheduler.Default); |
?????????現(xiàn)在我們已經(jīng)確保Task實(shí)例將在線程池執(zhí)行任務(wù)之前被存儲到theTask變量。因?yàn)榫€程池在Task對象調(diào)用Start()排隊(duì)任務(wù)之前無法獲得Task對象的引用,并且在這個時候,變量theTask已經(jīng)設(shè)置為Task的引用,與后面線程池訪問theTask.Id不會存在競爭問題。
?
推薦并行任務(wù)相關(guān)資源:《關(guān)于Async與Await的FAQ》
?
================================================================================================
?
園友提醒:(.NET4.5對.NET4.0的并行任務(wù)進(jìn)行過改進(jìn),然而我正式學(xué)習(xí)并行任務(wù)的時候已經(jīng)是.NET4.5,所以對于新改進(jìn)的API沒有進(jìn)行整理了,這邊有園友提醒,做下記錄,方便大家。)
? ? ??@zhangweiwen(Task.Run()是.net4.5新提供的API)
?
================================================================================================
?
?????????Ok,看完此文,相信你對并行任務(wù)中關(guān)于任務(wù)開啟又有更深入的理解了,(*^_^*),喜歡還請多多推薦。
?
原文:http://blogs.msdn.com/b/pfxteam/archive/2012/01/14/10256832.aspx
作者:Stephen Toub
?
?
作者:滴答的雨
出處:http://www.cnblogs.com/heyuquan/
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。
總結(jié)
以上是生活随笔為你收集整理的【转】1.A(译).NET4.X 并行任务中Task.Start()的FAQ的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手上有5000块钱随时用的钱,除了放支付
- 下一篇: 【转】1.9 Asp.Net Core