C#多執行緒開發-任務並行庫04

—阿輝發表於2021-09-09

你好,我是阿輝。

之前學習了執行緒池,知道了它有很多好處。

使用執行緒池可以使我們在減少並行度花銷時節省作業系統資源。可認為執行緒池是一個抽象層,其向程式設計師隱藏了使用執行緒的細節,使我們可以專心處理程式邏輯,而不是各種執行緒問題。

但也不是說我們所有的專案中都上執行緒池,其實它也有很多弊端,比如我們需要自定義使用非同步委託的方式才可以將執行緒中的訊息或異常傳遞出來。這些如果在一個大的軟體系統中,會導致軟體結構過於混亂,各個執行緒之間訊息傳遞來傳遞去的,如果發生沒有處理掉的異常,很容易導致軟體出現致命錯誤。

為了解決這個問題,在.Net Framework 4.0中引入了一個新的非同步操作的API,它叫任務並行庫(TPL)。

那麼接下來,讓我們一起來認識一下這個TPL,看看它到底有什麼魔力可以把執行緒池中的棘手問題解決掉。

任務並行庫

TPL又被認為是執行緒池的有一個抽象,其對程式設計師隱藏了執行緒池互動的底層程式碼,並只提供了更方便的細粒度的API。

TPL的核心是任務。一個任務代表一個非同步操作,該操作可以通過多種方式執行,可以使用或不使用獨立執行緒執行。

TPL有一個關鍵優勢,就是一個任務可以通過多種方式和其它任務組合起來。

比如可以同時開啟多個任務,等待所有任務完成,然後執行一個任務對之前所有任務的結果進行一些計算。

可以使用AggregateException來捕獲底層任務內部所有異常,並允許單獨處理這些異常。在C#5.0中已經內建了對TPL的支援,允許我們使用心得await和async關鍵字以平滑的、舒服的方式操作任務。

一、建立任務

可以通過下面三種方式來建立任務。

      var a1 = new Task(()=>TastMethod("執行緒01"));
      a1.Start();

      Task.Run(()=>TastMethod("執行緒001"));    
      Task.Factory.StartNew(()=>TastMethod("執行緒02"));
      Task.Factory.StartNew(() => TastMethod("執行緒03"),TaskCreationOptions.LongRunning);

      Console.ReadKey();

例項化的Tast屬性,必須進行啟動,任務才可以執行。其餘的.NET已經做了內建,只需要使用就預設自動開啟。

線上程3開啟過程中,增加了TaskCreationOptions.LongRuning引數,它表示標記該任務為長時間執行,結果該任務將不會使用執行緒池,而在單獨的執行緒中執行。然而根據執行該任務的當前任務排程程式,執行方式可能不同。

二、使用任務執行基本操作

下面介紹下從任務中得到其計演算法返回的結果。

        static void Main(string[] args)
        {   
            var a1 = new Task<int>(()=>TastMethod("執行緒01"));
            a1.Start();
            int result = a1.Result;            
            Console.WriteLine("result:" + result);
            Console.ReadKey();
        }

        static int TastMethod(string name) 
        {
            Console.WriteLine("執行緒名字:"+name+"Id:"+Thread.CurrentThread.ManagedThreadId+"是否屬於執行緒池:"+Thread.CurrentThread.IsThreadPoolThread);
            return 40;
        }

輸出結果

這裡我們宣告並執行了執行緒01並等待結果,該任務會被放置線上程池中,並且主執行緒會等待,直到任務返回前一直處於阻塞狀態。

其實也可以呼叫方法RunSynchronously()方法,使其特定執行在主執行緒。這是一個非常好的優化,可以避免使用執行緒池來執行非常短暫的操作。

三、處理任務中的異常

在非同步任務中,對於異常的處理是非常重要的。

            try
            {
                var a1 = new Task<int>(() => TastMethod("執行緒01",2));
                a1.Start();
                int result = a1.Result;
                Console.WriteLine("result:" + result);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }      

當程式啟動時,建立了一個任務並嘗試同步獲取任務結果。Result屬性的Get部分會使當前執行緒等待直到該任務結束,並將異常傳播給當前執行緒。此時通過try/catch是很容易捕獲到的(需要注意AggregateExceptiont,它被封裝起來,)。

int result = a1.GetAwaiter().GetResult ;

上面這種情況無需封裝異常,可以使用GetAwaiter和GetResult方法來訪問任務結果。

小寄語

人生短暫,我不想去追求自己看不見的,我只想抓住我能看的見的。

原創不易,給個關注。

我是阿輝,感謝您的閱讀,如果對你有幫助,麻煩點贊、轉發 謝謝。

相關文章