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
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...