C# Task

来源:https://www.cnblogs.com/loverwangshan/archive/2019/02/27/10444773.html
-Advertisement-
Play Games

在https://www.cnblogs.com/loverwangshan/p/10415937.html中我們有講到委托的非同步方法,Thread,ThreadPool,然後今天來講一下Task, ThreadPool相比Thread來說具備了很多優勢,但是ThreadPool卻又存在一些使用上的 ...


https://www.cnblogs.com/loverwangshan/p/10415937.html中我們有講到委托的非同步方法,Thread,ThreadPool,然後今天來講一下Task,

ThreadPool相比Thread來說具備了很多優勢,但是ThreadPool卻又存在一些使用上的不方便。比如:

  1. ThreadPool不支持線程的取消、完成、失敗通知等交互性操作
  2.  ThreadPool不支持線程執行的先後次序

以往,如果開發者要實現上述功能,需要完成很多額外的工作,現在.netFramwork3.0出現的Task,線程是基於線程池,然後提供了豐富的API

下麵我們來初步認識一下Task,下麵我們先新增一個公共的方法以下方法的演示中都會用到:

 1  #region Private Method
 2  /// <summary>
 3  /// 一個比較耗時耗資源的私有方法
 4  /// </summary>
 5  /// <param name="name"></param>
 6  private void DoSomethingLong(string name)
 7  {
 8      Console.WriteLine($"****DoSomethingLong Start  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}*****");
 9      long lResult = 0;
10      for (int i = 0; i < 1000000000; i++)
11      {
12          lResult += i;
13      }
14      Console.WriteLine($"****DoSomethingLong   End  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}****");
15  }
16  #endregion
View Code

 

一:Task啟動任務的幾種方式

 1  {
 2      Task task = new Task(() => this.DoSomethingLong("btnTask_Click_1"));
 3      task.Start();
 4  }
 5  {
 6      Task task = Task.Run(() => this.DoSomethingLong("btnTask_Click_2"));
 7  }
 8  {
 9      TaskFactory taskFactory = Task.Factory;// Task.Factory等同於: new TaskFactory()
10      Task task = taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_3"));
11  }

 

二:Task.Delay()和Thread.Sleep()方法的比較運用

 1 private void Test()
 2 {
 3     Console.WriteLine($"****************btnTask_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
 4     {
 5         Stopwatch stopwatch = new Stopwatch();
 6         stopwatch.Start();
 7         Console.WriteLine("在Sleep之前");
 8         Thread.Sleep(2000); //同步等待--當前線程等待2s 然後繼續
 9         Console.WriteLine("在Sleep之後");
10         stopwatch.Stop();
11         Console.WriteLine($"Sleep耗時{stopwatch.ElapsedMilliseconds}");
12     }
13     {
14         Stopwatch stopwatch = new Stopwatch();
15         stopwatch.Start();
16         Console.WriteLine("在Delay之前");
17         Task task = Task.Delay(2000)
18             .ContinueWith(t =>
19             {
20                 stopwatch.Stop();
21                 Console.WriteLine($"Delay耗時{stopwatch.ElapsedMilliseconds}");
22                 Console.WriteLine($"This is ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
23             });//非同步等待--等待2s後啟動新任務
24         Console.WriteLine("在Delay之後");
25     }
26     Console.WriteLine($"****************btnTask_Click End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
27 }
View Code

通過執行結果如下:

通過觀察發現:

  • Sleep是同步執行方法,即是遇到Sleep先等待,然後才能接著做其它的
  • Delay是非同步執行方法,一般不會單獨使用,而是會跟ContinueWith等一起聯合使用,即是重新開啟一個線程,這個線程多少時間之後執行!

三:TaskFactory類的ContinueWhenAny 和 ContinueWhenAll

 1  
 2   /// <summary>
 3   /// 模擬Coding過程
 4   /// </summary>
 5   /// <param name="name"></param>
 6   /// <param name="projectName"></param>
 7   private void Coding(string name, string projectName)
 8   {
 9       Console.WriteLine($"***Coding Start {name} {projectName}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***");
10       long lResult = 0;
11       for (int i = 0; i < 1000000000; i++)
12       {
13           lResult += i;
14       }
15 
16       Console.WriteLine($"***Coding   End {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***");
17   }
18   private void Test()
19   {
20       Console.WriteLine($"***btnTask_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***");
21       TaskFactory taskFactory = new TaskFactory();
22       List<Task> taskList = new List<Task>();
23       taskList.Add(taskFactory.StartNew(o => this.Coding("AA", "Portal"), "AA"));
24       taskList.Add(taskFactory.StartNew(o => this.Coding("BB", "  DBA "), "BB"));
25       taskList.Add(taskFactory.StartNew(o => this.Coding("CC", "Client"), "CC"));
26       taskList.Add(taskFactory.StartNew(o => this.Coding("DD", "BackService"), " DD"));
27       taskList.Add(taskFactory.StartNew(o => this.Coding("EE", "Wechat"), "EE"));
28 
29       //誰第一個完成,獲取一個紅包獎勵
30       taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"{t.AsyncState}開發完成,獲取個紅包獎勵{Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
31       taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"開發都完成,一起慶祝一下{Thread.CurrentThread.ManagedThreadId.ToString("00")}")));
32       //ContinueWhenAny  ContinueWhenAll 非阻塞式的回調;而且使用的線程可能是新線程,也可能是剛完成任務的線程,唯一不可能是主線程
33       Console.WriteLine($"***btnTask_Click end {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***");
34   }
View Code

通過結果我們總結如下:

  • ContinueWhenAny:等待任意一條完成
  • ContinueWhenAll :等待所有的完成
  • ContinueWhenAny 和 ContinueWhenAll 非阻塞式的回調;而且使用的線程可能是新線程,也可能是剛完成任務的線程,唯一不可能是主線程

 

四:Task中的 WaitAny 和 WaitAll

 1  /// <summary>
 2  /// 模擬Coding過程
 3  /// </summary>
 4  /// <param name="name"></param>
 5  /// <param name="projectName"></param>
 6  private void Coding(string name, string projectName)
 7  {
 8      Console.WriteLine($"***Coding Start {name} {projectName}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***");
 9      long lResult = 0;
10      for (int i = 0; i < 1000000000; i++)
11      {
12          lResult += i;
13      }
14      Thread.Sleep(2000);
15      Console.WriteLine($"***Coding   End {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***");
16  }
17  private void Test()
18  {
19      Console.WriteLine($"***btnTask_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***");
20      TaskFactory taskFactory = new TaskFactory();
21      List<Task> taskList = new List<Task>();
22      taskList.Add(taskFactory.StartNew(o => this.Coding("AA", "Portal"), "AA"));
23      taskList.Add(taskFactory.StartNew(o => this.Coding("BB", "  DBA "), "BB"));
24      taskList.Add(taskFactory.StartNew(o => this.Coding("CC", "Client"), "CC"));
25      taskList.Add(taskFactory.StartNew(o => this.Coding("DD", "BackService"), " DD"));
26      taskList.Add(taskFactory.StartNew(o => this.Coding("EE", "Wechat"), "EE"));
27     
28      //阻塞當前線程,等著任意一個任務完成
29      Task.WaitAny(taskList.ToArray());//也可以限時等待,如果是winform界面會卡
30      Console.WriteLine("第一個模塊已經完成,現在開始準備環境開始部署");
31      //需要能夠等待全部線程完成任務再繼續  阻塞當前線程,等著全部任務完成,如果是winform界面會卡
32      Task.WaitAll(taskList.ToArray());
33      Console.WriteLine("5個模塊全部完成,準備聯測");
34      Console.WriteLine($"***btnTask_Click end {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***");
35  }
View Code

運行執行如下:

通過上面得到如下:

  • Task.WaitAny:等待任意一條完成。例如:核心數據可能來自資料庫/介面服務/分散式搜索引擎/緩存,多線程併發請求,哪個先完成就用哪個結果,其他的就不管了
  • Task.WaitAll :等待所有的完成。例如:A資料庫 B介面 C分散式服務 D搜索引擎,適合多線程併發,都完成後才能返回給用戶,需要等待WaitAll
  • Task.WaitAny 和Task.WaitAll都是阻塞當前線程,等任務完成後執行操作, 阻塞卡界面,是為了併發以及順序控制

 

五:Task想要控制先後順序,可以通過ContinueWith

這個在什麼的Delay中已經看到了,可以一直使用ContinueWith來增加任務

1 Task.Run(() => this.DoSomethingLong("btnTask_Click")).ContinueWith(t => Console.WriteLine($"btnTask_Click已完成{Thread.CurrentThread.ManagedThreadId.ToString("00")}"));//回調

六:任務想要有返回值,並且把返回值傳遞到ContinueWith中,可以通過如下代碼實現:

1  Task.Run<int>(() =>
2                 {
3                     Thread.Sleep(2000);
4                     return DateTime.Now.Year;
5                 }).ContinueWith(tInt =>
6                 {
7                     int i = tInt.Result; //有堵塞
8                 });

註意:使用.Result這個會堵塞線程

 

七:Task的線程是源於線程池,線程池是單例的,全局唯一

我們可以通過下麵代碼來測試一下:

 1 ThreadPool.SetMaxThreads(8, 8);
 2 for (int i = 0; i < 100; i++)
 3 {
 4     int k = i;
 5     Task.Run(() =>
 6     {
 7         Console.WriteLine($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
 8         Thread.Sleep(2000);
 9     });
10 }

設置線程池最大線程為8個後,我們通過監測結果發現:同時併發的Task只有8個,而且線程Id是重覆出現的,即線程是復用的。所以線程池是是全局的,以後要慎重設置線程池數量。

 

Parallel是來源於命名空間為:System.Threading.Tasks,併發執行多個Action 多線程。主線程會參與計算---阻塞界面,等於TaskWaitAll+主線程計算

具體的一些用法如下:

 1 {
 2     //啟動5個任務
 3     Parallel.Invoke(() => this.DoSomethingLong("btnParallel_Click_1"),
 4         () => this.DoSomethingLong("btnParallel_Click_2"),
 5         () => this.DoSomethingLong("btnParallel_Click_3"),
 6         () => this.DoSomethingLong("btnParallel_Click_4"),
 7         () => this.DoSomethingLong("btnParallel_Click_5"));
 8 }
 9 {
10     //啟動5個任務,i是從0-4
11     Parallel.For(0, 5, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
12 }
13 {
14     //啟動5個任務
15     Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
16 }

上面三種方法都是可以的。然後Parallel是線程阻塞,那我們可不可以不堵塞線程呢,這個我們可以重啟一個任務,讓子線程去做這個事情,如下:

1                 Task.Run(() =>
2                 {
3                     ParallelOptions options = new ParallelOptions();
4                     options.MaxDegreeOfParallelism = 3;
5                     Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
6                 });

這是一種思路,以後會有很多地方用到。

 

九:控制線程數量

上面的Task和TaskFactory以及Parallel都已經學習完了,我們曉的線程是根據電腦資源有關係的,有時候批量啟動N個線程,效率還不如單線程高,因為會有線程切換是要耗時的,那我們怎麼控制保證一次調用多個線程呢,下麵提供Task和Parallel的兩種解決方案,具體如下:

 1 {
 2     //Parallel設置最大線程為3
 3     ParallelOptions options = new ParallelOptions();
 4     //設置最大線程為3,以後Parallel想要控制線程數量只需要設置ParallelOptions類中的MaxDegreeOfParallelism即可
 5     options.MaxDegreeOfParallelism = 3;
 6     Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
 7 }
 8 {
 9     //Task設置最大線程為3
10     List<Task> taskList = new List<Task>();
11     for (int i = 0; i < 10000; i++)
12     {
13         int k = i;
14         if (taskList.Count(t => t.Status != TaskStatus.RanToCompletion) >= 3)
15         {
16             Task.WaitAny(taskList.ToArray());
17             taskList = taskList.Where(t => t.Status != TaskStatus.RanToCompletion).ToList();
18         }
19         taskList.Add(Task.Run(() =>
20         {
21             Console.WriteLine($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
22             Thread.Sleep(2000);
23         }));
24     }
25 }
View Code

 

學完上面的非同步方法,Thread和Task,有人會提出以下問題:

1:什麼時候能用多線程? 任務能併發的時候能夠使用多線程
2:多線程能幹嘛?多線程能夠提升速度/優化用戶體驗,以cpu資源來換時間


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

-Advertisement-
Play Games
更多相關文章
  • 一、概述 docker的網路驅動有很多種方式,按照docker官網給出的網路解決方案就有6種,分別是:bridge、host、overlay、macvlan、none、Network plugins,每個網路都有自己的特點,當然應用場景也不同,比如當有多台主機上的docker容器需要容器間進行跨宿主 ...
  • ##sql語句為```SELECT COUNT(id) AS tp_count FROM `tableName` WHERE `status` = 0 AND `source` = 1 AND ( `end_time`-`add_time` > 2592000 AND `end_time`-`add... ...
  • 在項目開發中需要Thinkphp5讀取多個資料庫的數據,本文詳細介紹Thinkphp5多資料庫切換 一、在database.php配置預設資料庫連接 'type' => 'mysql','hostname' => '伺服器IP地址','database' => '資料庫名','username' = ...
  • 慢查詢日誌會將查詢過程中超出你設置的時間的查詢記錄下來,以便供開發者進行分析和優化。 1. 開啟慢查詢 1.1 查看當前設置 輸出 三個參數 slow_query_log ON/OFF ,使能開關 slow_query_log_file 慢查詢日誌目錄和文件名稱 long_query_time 超過 ...
  • 安裝包鏈接:https://pan.baidu.com/s/1WsQTeEQClM88aEqIvNi2ag 提取碼:s241 rlwrap-0.37-1.el6.x86_64.rpm 和 rlwrap-0.37-1.el6.i686.rpm,安裝perl依賴後如果還提示需要安裝perl,實則需要安裝 ...
  • 一、SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". 這個報警告的原因簡單來說時因為slf4j的版本和log4j的版本不匹配。 解決辦法: 1.在你的maven庫中查找你的slf4j版本,若有兩個,最後選版本低的,因為本 ...
  • 筆記記錄自林曉斌(丁奇)老師的《MySQL實戰45講》 4) --深入淺出索引(上) 一句話簡單來說,索引的出現其實就是為了提高數據查詢的效率,就像書的目錄一樣。 索引的常見模型 哈希表:哈希表是一種以Key-Value存儲數據的結構,只要輸入key,就可以找到對應的value。哈希的思路很簡單, ...
  • 1、通過information_schema.COLUMNS表 查詢該表可得到所需欄位信息 如下圖所示: 2、示例 下麵截圖是示例: SQL語句如下 3、導出Excel 點擊導出結果即可導出 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...