Task
創建每個線程需要佔用1MB的虛擬內存,並且線程過多會導致CPU耗費大量時間在切換不同線程上。所以在之前我們會使用線程池來自動分配線程。
在.Net Framwork 4,TPL提供了一個新的方式去創建線程,那就是Task類,它告訴任務調度器有異步工作需要做,任務調度器有多重測量,但是默認是從線程池請求一個線程給Task類使用。
Task類是異步執行一個工作任務,而委託(例如:Action)是同步執行一個工作任務。所以Task是將委託(Action)從同步執行模式轉變爲異步執行。
Task.Run()是直接熱啓動,也就是立刻執行,而如果通過Task構造函數實例化的任務就是冷啓動,不會立即執行。熱啓動任務的狀態是不確定的。當遇上一組Task正在運行,而我們需要等待所有任務都完成時就會使用Task.WaitAll(),只需要等待任一任務完成就使用Task.WaitAny();
如果我們想要這個任務返回一個結果,我們需要使用Task<T>類型異步運行一個Func<T>.例如:
Task<string> task = Task.Run<string>(() => { return string.Empty; });
任務結束之後,IsCompleted屬性設置爲true,但是該任務實際上運行可能是正常的,也可能是出錯結束。需要讀取Task.Status屬性得到具體狀態,其中只要Status爲RuntoCompletion / Canceled / Faulted,都會設置IsCompleted=true。
Task.CurrentId是唯一性標識,在調試的時候非常有用,另外AsyncState可以爲任務關聯額外數據,將需要開啓這個Task的源對象或者某一些Target對象帶入Task內部。
控制流
控制流實際上就是系統決定接下來要做什麼?例如Console.WriteLine(str.GetHashCode());那麼這句代碼的控制流就是首先計算出str的HashCode值,然後將這個返回值作爲WriteLine的參數,在這個過程中就產生了控制點的延續,實際上C#編程是控制流延續直到結束。任何給定代碼的延續實際上是分爲兩種可能,“正常”和“異常”。
普通的同步操作是自上而下的延續控制流,但是異步任務是在原有的控制流上添加了一個新的分支維度,也就是原有的Task.Start()語句執行後,控制流繼續往下執行,同時Task委託的主體代碼也開始執行了,在原有控制流的基礎上增加了一條分支控制流開始執行。
Task.ContinueWith()
使用CountinueWith()方法可以串行的鏈接運行任務,只有前面一個任務完成之後纔可以繼續完成第二個任務。同時該方法返回的也是Task對象,還可以繼續串接第三個任務,所以是可以鏈接任意長度的任務鏈的。
ContinueWith()有很多重載,其中有一個ContinueWithOptions枚舉值可以提供多重串行運行可能性
枚舉值 | 具體應用 |
None | 默認爲前面任務完成,不管任務狀態是什麼都運行下一個任務 |
PreferFairness | 公平調度,不保證誰先運行 |
LongRunning | 告訴調度器這是一個I/O受限的高延遲任務,調度器可先處理後面的其他工作,少用 |
AttachedToParent | 指定任務連接到任務層次結構中的父任務 |
DenyChildAttach | 試圖創建子任務將引發異常 |
NotOnRanToCompletion | 如果延續任務的先驅任務成功完成,則不應該調度延續任務。 |
NotOnFaulted | 如果延續任務的先驅任務失敗,引發了異常,則不應該調度延續任務。 |
NotOnCanceled | 如果延續任務的先驅任務取消了,則不應該調度延續任務。 |
OnlyOnCanceled | 如果延續任務的先驅任務取消了,才調度該延續任務。 |
OnlyOnFaulted | 如果延續任務的先驅任務失敗,引發了異常,才調度延續任務 |
OnlyOnRanToCompletion | 如果延續任務的先驅任務成功完成,才應該調度延續任務。 |
ExecuteSynchronously | 直接在先驅任務的線程上繼續運行,而無需重新從線程池拿線程。 |
HideScheduler | 隱藏線程調度器 |
LazyCancellation |
延續任務將監視取消的標誌的時間推遲到前驅任務結束之後。 有延續任務t1、t2、t3,其中t2任務在t1完成前取消了,那麼t3可能會在t1完成前開始,這個參數可以避免這種問題。 |