並行編程和任務(二)

来源:https://www.cnblogs.com/hulizhong/archive/2019/11/08/11812428.html
-Advertisement-
Play Games

前言 上一篇我們主要介紹了並行編程相關的知識,這一節我們繼續介紹關於任務相關的知識。為了更好的控制並行操作,我們可以使用System.Threading.Tasks中的Task類。我們首先來瞭解是什麼是任務——任務表示將要完成的一個或某個工作單元,這個工作單元可以在單獨線程中運行,也可以使用同步方式 ...


前言

  上一篇我們主要介紹了並行編程相關的知識,這一節我們繼續介紹關於任務相關的知識。為了更好的控制並行操作,我們可以使用System.Threading.Tasks中的Task類。我們首先來瞭解是什麼是任務——任務表示將要完成的一個或某個工作單元,這個工作單元可以在單獨線程中運行,也可以使用同步方式啟動運行(需要等待主線程調用)。為什麼使用任務呢?——任務不僅可以獲得一個抽象層(將要完成的工作單元)、還可以對底層的線程運行進行更好更多的控制(任務的運行)。

使用線程池的任務

  我們講到使用任務可以更好更多的控制底層的線程。就涉及到——線程池,線程池提供的是一個後臺線程的池。線程池獨自管理線程、根據需求增加或減少線程數。使用完成的線程返回至線程池中。我們下麵就看看創建任務:

我們看下創建任務的幾種方式:

1、使用實例化的TaskFactory類,然後使用其StartNew方法啟動任務。

2、使用Task靜態的Factory以來訪問TaskFactory,然後調用StartNew方法啟動任務。與第一種相似,但是對工廠的創建的控制就沒那麼全面。

3、使用Task的構造函數,實例化Task對象來指定創建任務,然後通過Start()方法進行啟動任務。

4、使用Task.Run方法來立即啟動任務。

  我們看下以上方法創建的任務有何區別和相同吧,看代碼:

        private static object _lock = new object();
     public
static void TaskMethond(object item) { lock (_lock) { Console.WriteLine(item?.ToString()); Console.WriteLine($"任務Id:{Task.CurrentId?.ToString() ?? "沒有任務運行"}\t 線程Id:{Thread.CurrentThread.ManagedThreadId}"); Console.WriteLine($"是否是線程池中的線程:{Thread.CurrentThread.IsThreadPoolThread}"); Console.WriteLine($"是否是後臺線程:{Thread.CurrentThread.IsBackground}"); Console.WriteLine(); } } #region 任務創建 public static void TaskCreateRun() { var taskFactory = new TaskFactory(); var task1 = taskFactory.StartNew(TaskMethond, "使用實例化TaskFactory"); var task2 = Task.Factory.StartNew(TaskMethond, "使用Task靜態調用Factory"); var task3 = new Task(TaskMethond, "使用Task構造函數實例化"); task3.Start(); var task4 = Task.Run(() => TaskMethond("使用Task.Run")); } #endregion

  我們看代碼運行的結果,發現不管使用的那種方法創建任務,都是使用過的線程池中的線程。

使用單獨線程的任務

  任務當然也不一定就是使用線程池中的線程運行的,也是可以使用其他線程的。如果任務的將長時間運行的話,我們儘可能的考慮使用單獨線程運行(TaskCreationOptions.LongRunning),這個情況下線程就不由線程池管理。我們看下不使用線程池中線程運行,使用單獨線程運行的任務。

        #region 單獨線程運行任務
        public static void OnlyThreadRun()
        {
            var task1 = new Task(TaskMethond, "單獨線程運行任務", TaskCreationOptions.LongRunning);
            task1.Start();
        }
        #endregion

  我們看其運行結果,依舊是後臺線程,但是不是使用的線程池中的線程。

使用同步任務

同時任務也可以同步運行,以相同的線程作為調用線程,下麵我們看下使用Task類中的RunSynchronoushly方法實現同步運行。

 

        #region 同步任務

        public static void TaskRunSynchronoushly()
        {
            TaskMethond("主線程調用");
            var task1 = new Task(TaskMethond, "任務同步調用");
            task1.RunSynchronously();
        }
        #endregion

 

 

我們看運行結果,發現首先調用TaskMethond方法時候沒有任務並且使用的線程1,再我們創建Task實例運行TaskMethond方法的時候,任務id是1,但是線程我們依然使用的是主線程1。 

連續任務

在任務中,我們可以指定在某個任務完成後,應該馬上開始另外一個任務。好比一個任務完成之後應該繼續其處理。但是失敗後我們應該進行一些處理工作。

我們可以使用ContinueWith()方法來定義使用連續任務,表示某任務之後應該開始其他任務,我們也可以指定任務成功後開始某個任務或者失敗後開啟某個任務(TaskContinuationOptions)。

        #region 連續任務
        public static void TaskOne()
        {
            Console.WriteLine($"任務{Task.CurrentId},方法名:{System.Reflection.MethodBase.GetCurrentMethod().Name }啟動");
            Task.Delay(1000).Wait();
            Console.WriteLine($"任務{Task.CurrentId},方法名:{System.Reflection.MethodBase.GetCurrentMethod().Name }結束");
        }
        public static void TaskTwo(Task task)
        {
            Console.WriteLine($"任務{task.Id}以及結束了");
            Console.WriteLine($"現在開始的 任務是任務{Task.CurrentId},方法名稱:{System.Reflection.MethodBase.GetCurrentMethod().Name  }  ");
            Console.WriteLine($"任務處理");
            Task.Delay(1000).Wait();
        }
        public static void TaskThree(Task task)
        {
            Console.WriteLine($"任務{task.Id}以及結束了");
            Console.WriteLine($"現在開始的 任務是任務{Task.CurrentId}.方法名稱:{System.Reflection.MethodBase.GetCurrentMethod().Name } ");
            Console.WriteLine($"任務處理");
            Task.Delay(1000).Wait();
        }
        public static void ContinueTask()
        {
            Task task1 = new Task(TaskOne);
            Task task2 = task1.ContinueWith(TaskTwo, TaskContinuationOptions.OnlyOnRanToCompletion);//已完成情況下繼續任務
            Task task3 = task1.ContinueWith(TaskThree, TaskContinuationOptions.OnlyOnFaulted);//出現未處理異常情況下繼續任務
            task1.Start();
        }
        #endregion

我們看代碼中寫的是先開始運行TaskOne(),然後當任務完成後運行TaskTwo(Task task) ,如果任務失敗的話機會運行TaskThree(Task task)。我們看運行結果中是運行了TaskOne()然後成功後運行了TaskTwo(Task task),避開了TaskThree(Task task)的運行,所以我們是可以通過ContinueWith來進行連續任務和TaskContinuationOptions進行控制任務運行的。

任務層次—父子層次結構

這裡我們利用任務的連續性,我就就可以實現在一個任務結束後立即開啟另一個任務,任務也可以構成一個層次結構。就比如一個任務中啟動了一個任務,這樣的情況就形成了父子層次的結構。下麵我們看的案例就是這麼一個案例。

 

        #region 任務的層次結構——父子層次結構
        public static void ChildTask()
        {
            Console.WriteLine("當前運行的子任務,開啟");
            Task.Delay(5000).Wait();
            Console.WriteLine("子任務運行結束");
        }

        public static void ParentTask()
        {
            Console.WriteLine("父級任務開啟");
            var child = new Task(ChildTask);
            child.Start();
            Task.Delay(1000).Wait();
            Console.WriteLine("父級任務結束");
        }

        public static void ParentAndChildTask()
        {
            var parent = new Task(ParentTask);
            parent.Start();
            Task.Delay(2000).Wait();
            Console.WriteLine($"父級任務的狀態 :{parent.Status}");
            Task.Delay(4000).Wait();
            Console.WriteLine($"父級任務的狀態 :{parent.Status}");
        }
        #endregion

 

 

等待任務

  在前面問介紹的.Net非同步編程中我們講到了WhenAll,用於處理多個非同步方法。在這裡我們繼續擴展點,WhenAll()和WaitAll(),都是等待傳遞給他們的任務完成。但是WaitAll()方法阻塞調用任務,知道所有任務完成為止,而WhenAll()返回了一個任務,從而可以使用async關鍵在等待結果。不會阻塞任務。與之相對應的也還有WaitAny()和WhenAn()。等待任務還有我們一直都用到了的Task.Delay()方法,指定這個方法放回的任務前要等待的毫秒數。

  下麵我們看這個ValueTask等待類型(結構),相對於Task類來說,ValueTask沒有堆中對象的開銷。在一般情況下,Task類型的開銷可以被忽略掉,但是在一些特殊情況下,例如方法被調用千次萬次來看。這種情況ValueTask就變得很適用了。我們看下麵這個案例,使用ValueTask時,在五秒內的情況下直接從它的構造函數返回值。如果時間不在五秒內的話就使用真正獲取數據的方法。然後我們與使用Task的方法進行對比。這裡我們採取十萬條數據的測試對比。

        #region 等待任務
        private static DateTime _time;
        private static List<string> _data;
        public static async ValueTask<List<string>> GetStringDicAsync()
        {
            if (_time >= DateTime.Now.AddSeconds(-5))
            {
                return await new ValueTask<List<string>>(_data);
            }

            else
            {
                (_data, _time) = await GetData();
                return _data; 
            }
        }
        public static Task<(List<string> data, DateTime time)> GetData() => 
            Task.FromResult(
                (Enumerable.Range(0, 10).Select(x => $"itemString{x}").ToList(), DateTime.Now));

        public static async Task<List<string>> GetStringList()
        {
            (_data, _time) = await GetData();
            return _data;
        }
        #endregion
        static async Task  Main(string[] args)
        { 
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            Console.WriteLine("ValueTask開始");
            for (int i = 0; i < 100000; i++)
            {
               var itemList= await GetStringDicAsync(); 
            }
            Console.WriteLine("ValueTask結束");
            Console.WriteLine($"ValueTask耗時:{stopwatch.ElapsedMilliseconds}");

            Console.WriteLine();
            Console.WriteLine();

            stopwatch.Restart();
            Console.WriteLine("Task開始");
            for (int i = 0; i < 100000; i++)
            {
                var itemList = await GetStringList(); 
            }
            Console.WriteLine("Task結束");
            Console.WriteLine($"Task耗時:{stopwatch.ElapsedMilliseconds}");
            Console.ReadLine();
        }

  我們看其運行結果,使用Task和ValueTask的運行結果耗時相差是巨大的。所以在一些特殊情況下使用ValueTask或許會更加的適用。

總結

  今天我們介紹了關於任務相關的一些知識概念。我們結合上一篇文章我們來梳理一些任務、線程、多線程、非同步、同步、併發、並行任務之間的聯繫與關係吧。

  首先我們看我們這章節學習的任務、任務是一個將要完成的工作單元,那麼由誰完成呢?由線程來運行這個任務。那麼關於多線程呢?多線程應該可以說是一個設計概念,用來實現線程切換的。多線程就可以運行多個任務,但是在併發中。在同一時間內只能有一個程式運行。只不過線程間切換速度極快,讓它看起來似乎是在同一時間運行了多個程式。其實在微觀上講,併發在任意時間點只有一個程式在運行,只不過是線程切換速度快。那麼怎麼達到切換速度極快呢?這就需要非同步了。線上程運行的時候不需要等到它完成結果再去繼續其他的線程任務。也就是可等待的。實現A運行起來不等待其結果,然後切換到B繼續運行。這樣切換速度就極快了。我們再仔細看多線程切換線程似乎成了實現非同步的一種方法手段了。有非同步就有同步,同步來說就不需要使用到多線程了,沒必要。反正等到上一個任務運行完成。就繼續使用上一個線程繼續運行。這裡都是講的併發中的情況。那麼並行呢?並行可以說不管在微觀還是巨集觀上都是可以實現一個時間運行多個程式的。併發是多個程式運行在一個處理機上,但是並行任務是運行在多個處理機上。例如實現四個任務並行,那麼我們至少需要四個邏輯處理內核的配合才能到達。

 項目源碼地址


  世界上那些最容易的事情中,拖延時間最不費力。堅韌是成功的一大要素,只要在門上敲得夠久夠大聲,終會把人喚醒的。

     歡迎大家掃描下方二維碼,和我一起學習更多的知識

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 還記得剛曾經因為導入導出不會做而發愁的自己嗎?我見過自己前同事因為一個導出改了好幾天,然後我們發現雖然有開源的庫但是用起來卻不得心應手,主要是因為百度使用方案的時候很多方案並不能解決問題。 尤其是嘗試新技術那些舊的操作還會有所改變,為了節約開發時間,我們把解決方案收入到一個個demo中,方便以後即拿 ...
  • using System; namespace ConsoleApp3 { class Program { static void Main(string[] args) { Console.Write("你要輸入多少項?"); int a = Convert.ToInt32(Console.Rea ...
  • Aspose.PSD for .NET高級PSD文件格式操作API,沒有任何Adobe Photoshop依賴項。API允許創建或編輯Photoshop文件,並提供更新圖層屬性,添加水印,執行圖形操作或將一種文件格式轉換為另一種文件的功能。 近日,Aspose.PSD for .NET更新至最新版本 ...
  • 背景 ASP.NET Core 支持依賴關係註入 (DI) 軟體設計模式,並且預設註入了很多服務,具體可以參考 "官方文檔" , 相信只要使用過依賴註入框架的同學,都會對此有不同深入的理解,在此無需贅言。 然而,在引入 IOC 框架之後,對於之前常規的對於類的依賴(new Class)變成通過構造函 ...
  • 保留兩位小數點 由於簡單的原因大家直接看代碼塊。 執行結果如下: ...
  • using System; namespace class1 { class program { static void Main(string[] args) { Console.Write("請輸入a="); double a = double.Parse(Console.ReadLine()) ...
  • 1.查詢生命周期 在進入正題時候,我們先來瞭解EF Core查詢的生命周期。 1.1LINQ查詢會由Entity Framework Core處理並生成給資料庫提供程式可處理的表示形式(說白了就是生成給資料庫可識別數據形式)。 ●發送的查詢結果(查詢表示形式)會被緩存,以便每次執行查詢時無需進行1. ...
  • using System; namespace class1 { class program { static void Main(string[] args) { //值傳遞引用,實際參數不會變化 Console.Write("請輸入a="); double a = double.Parse(Co ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...