在學習的過程中,看一些一線的技術文檔很吃力,而且考慮到國内那些技術牛人英語都不差的,要向他們看齊,是以每天下班都在瘋狂地背單詞,部落格有些日子沒有更新了,見諒見諒

Task Parallel Library (TPL), 在.NET Framework 4微軟推出TPL,并把TPL作為編寫多線程和并行代碼的首選方式,但是,在國内,到目前為止好像用的人并不多。(TPL)是System.Threading和System.Threading.Tasks命名空間中的一組公共類型和API 。TPL的目的是通過簡化向應用程式添加并行性和并發性的過程來提高開發人員的工作效率,TPL動态地擴充并發度,以最有效地使用所有可用的處理器。通過使用TPL,您可以最大限度地提高代碼的性能,讓我們專注于程式本身而不用去關注負責的多線程管理。
出自: https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl
在上面介紹了什麼是TPL,可能大家還是雲裡霧裡,不知道TPL的好處到底是什麼。
我在youtube上找到了一個優秀的視訊,講述的是TPL和Thread的差別,我覺得對比一下,TPL的優勢很快就能展現出來,如果大家能打開的話建議大家一定要看看。
位址是:https://www.youtube.com/watch?v=No7QqSc5cl8
現如今,我們的電腦的CPU怎麼也是2核以上,下面假設我的電腦是四核的,我們來做一個實驗。
代碼中,如果使用Thread來處理任務,如果不做特出的處理,隻是thread.Start(),監測電腦的核心的使用情況是下面這樣的。
每一條線代表CPU某個核心的使用情況,明顯,随着代碼Run起來,其實隻有某一個核心的使用率迅速提升,其他核心并無明顯波動,為什麼會這樣呢?
原來,預設情況下,作業系統并不會調用所有的核心來處理任務,即使我們使用多線程,其實也是在一個核心裡面運作這些Thread,而且Thread之間涉及到線程同步等問題,其實,效率也不會明顯提高。
在代碼中,引入了TPL來處理相同的任務,再次監視各個核心的使用情況,效果就變得截然不同,如下。
可以看到各個核心的使用情況都同時有了明顯的提高。
說明使用TPL後,不再是使用CPU的某個核心來處理任務了,而是TPL自動把任務分攤給每個核心來處理,處理效率可想而知,理論上會有明顯提升的(為什麼說理論上?和使用多線程一樣,各個核心之間的同步管理也是要占用一定的效率的,是以對于并不複雜的任務,使用TPL可能适得其反)。
實驗結果出自https://www.youtube.com/watch?v=No7QqSc5cl8
看了這個實驗講解,是不是了解了上面所說的這句。
TPL的目的是通過簡化向應用程式添加并行性和并發性的過程來提高開發人員的工作效率,TPL動态地擴充并發度,以最有效地使用所有可用的處理器。
是以說,使用TPL 來處理多線程任務可以讓你不必吧把精力放在如何提高多線程處理效率上,因為這一切,TPL 能自動地幫你完成。
TPL處理Dataflow是TPL強大功能中的一種,它提供一套完整的資料流元件,這些資料流元件統稱為TPL Dataflow Library,那麼,在什麼場景下适合使用TPL Dataflow Library呢?
官方舉的一個 栗子 再恰當不過:
例如,通過TPL Dataflow提供的功能來轉換圖像,執行光線校正或防紅眼,可以建立管道資料流元件,管道中的每個功能可以并行執行,并且TPL能自動控制圖像流在不同線程之間的同步,不再需要Thread 中的Lock。
TPL資料流庫由Block組成,Block是緩沖和處理資料的單元,TPL定義了三種最基礎的Block。
source blocks(System.Threading.Tasks.Dataflow.ISourceBlock <TOutput>),源塊充當資料源并且可以從中讀取。
target blocks(System.Threading.Tasks.Dataflow.ITargetBlock <TInput>),目标塊充當資料接收器并可以寫入。
propagator blocks(System.Threading.Tasks.Dataflow.IPropagatorBlock <TInput,TOutput>),傳播器塊充當源塊和目标塊,并且可以被讀取和寫入。它繼承自ISourceBlock <TOutput>和ITargetBlock <TInput>。
還有其他一些個性化的Block,但其實他們都是對這三種Block進行一些擴充,可以結合下面的代碼來了解這三種Block.
可以看到,我定義了BufferBlock和ActionBlock,它們分别繼承于ISourceBlock 和 ITargetBlock ,是以說,他們其實就是源塊和目标塊,在new actionBlock()中傳入了一個Action<String>,該Action就是該Block所執行的任務。 最後,DataflowBlock.Encapsulate(actionBlock, bufferBlock)把源塊和目标塊合并成了一個傳遞塊。
TransfromBlock繼承了IPropagatorBlock,是以它本身就是一個傳遞塊,是以它除了要處理出入資料,還要傳回資料,是以給new TransformBlock()中傳入的是Func<TInput, TOutput>而不是Action<TInput>.
TargetBlock隻能寫入并處理資料,不能讀取,是以TargetBlock适合作為Pipeline的最後一個Block。
在在構造TargetBlock(包括其子類)的時候,可以傳入ExecutionDataflowBlockOptions參數,ExecutionDataflowBlockOptions對象裡面有一個MaxDegreeOfParallelism屬性,通過改制,可以控制該Block的同時處理任務的數量(可以了解成線程數)。
通過
ISourceBlock<TOutput>.LinkTo(ITargetBlock<TOutput> target, DataflowLinkOptions linkOption)
方法,可以把Block連接配接起來,即建構Pipeline,當DataflowLinkOptions對象的PropagateCompletion屬性為true時,SorceBlock任務處理完成是,會把TargetBlock也标記為完成。
Block被标記為Complete 後,無法傳入新的資料了,即不能再處理新的任務了。
Pipeline建構好後,我們隻需要給第一個Block傳入資料,該資料就會在管道内流動起來了,所有資料傳入完成後,調用Block的Complete方法,把該Block标記為完成,就不可以再往裡面Post資料了。
完整代碼如下:
Main方法如下:
測試運作如圖:
我來解釋一下,為什麼是這麼運作的,因為把管道的并行度設定為2,是以每個Block可以同時處理兩個任務,是以,如果給管道傳入四個字元 ,每個字元作為一個任務,假設傳入 “碼農阿宇”四個任務,會時這樣的一個過程…..
碼 農 兩個首先進入Process1,
處理完成後,碼 農 兩個任務流出,
Process1位置空出來, 阿 宇 兩個任務流入 Process1,
碼 農 兩個任務流向 Process2,
阿 宇 從 Process1 處理完成後流出,此時Process1任務完成
碼 農 流出 Process2 ,同時 阿 宇 流入 Process2 ……
依此類推….
該項目Github位址: https://github.com/liuzhenyulive/Tpl-Dataflow-Demo
參考文獻:https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/dataflow-task-parallel-library
碼字不易,如果對您有用,歡迎推薦和關注,謝謝
!