成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

C#多線程和異步之Task和async/await詳解

開發 后端
同步和異步主要用于修飾方法。當一個方法被調用時,調用者需要等待該方法執行完畢并返回才能繼續執行,我們稱這個方法是同步方法;當一個方法被調用時立即返回,并獲取一個線程執行該方法內部的業務,調用者不用等待該方法執行完畢,我們稱這個方法為異步方法。

 [[381489]]

一、什么是異步

同步和異步主要用于修飾方法。當一個方法被調用時,調用者需要等待該方法執行完畢并返回才能繼續執行,我們稱這個方法是同步方法;當一個方法被調用時立即返回,并獲取一個線程執行該方法內部的業務,調用者不用等待該方法執行完畢,我們稱這個方法為異步方法。

異步的好處在于非阻塞(調用線程不會暫停執行去等待子線程完成),因此我們把一些不需要立即使用結果、較耗時的任務設為異步執行,可以提高程序的運行效率。net4.0在ThreadPool的基礎上推出了Task類,微軟極力推薦使用Task來執行異步任務,現在C#類庫中的異步方法基本都用到了Task;net5.0推出了async/await,讓異步編程更為方便。本篇主要介紹Task、async/await相關的內容,其他異步操作的方式會在下一篇介紹。

二、Task介紹

Task是在ThreadPool的基礎上推出的,我們簡單了解下ThreadPool。ThreadPool中有若干數量的線程,如果有任務需要處理時,會從線程池中獲取一個空閑的線程來執行任務,任務執行完畢后線程不會銷毀,而是被線程池回收以供后續任務使用。當線程池中所有的線程都在忙碌時,又有新任務要處理時,線程池才會新建一個線程來處理該任務,如果線程數量達到設置的最大值,任務會排隊,等待其他任務釋放線程后再執行。線程池能減少線程的創建,節省開銷,看一個ThreadPool的栗子吧

  1. static void Main(string[] args) 
  2.         { 
  3.             for (int i = 1; i <=10; i++) 
  4.             { 
  5.                 //ThreadPool執行任務 
  6.                 ThreadPool.QueueUserWorkItem(new WaitCallback((obj) => { 
  7.                     Console.WriteLine($"第{obj}個執行任務"); 
  8.                 }),i); 
  9.             } 
  10.             Console.ReadKey(); 
  11.         } 

上邊的代碼通過ThreadPool執行了10個任務,執行結果為:

ThreadPool相對于Thread來說可以減少線程的創建,有效減小系統開銷;但是ThreadPool不能控制線程的執行順序,我們也不能獲取線程池內線程取消/異常/完成的通知,即我們不能有效監控和控制線程池中的線程。

1 Task創建和運行

我們知道了ThreadPool的弊端:我們不能控制線程池中線程的執行順序,也不能獲取線程池內線程取消/異常/完成的通知。net4.0在ThreadPool的基礎上推出了Task,Task擁有線程池的優點,同時也解決了使用線程池不易控制的弊端。

首先看一下怎么去創建并運行一個Task,Task的創建和執行方式有如下三種:

  1. static void Main(string[] args) 
  2.         { 
  3.             //1.new方式實例化一個Task,需要通過Start方法啟動 
  4.             Task task = new Task(() => 
  5.             { 
  6.                 Thread.Sleep(100); 
  7.                 Console.WriteLine($"hello, task1的線程ID為{Thread.CurrentThread.ManagedThreadId}"); 
  8.             }); 
  9.             task.Start(); 
  10.  
  11.             //2.Task.Factory.StartNew(Action action)創建和啟動一個Task 
  12.             Task task2 = Task.Factory.StartNew(() => 
  13.               { 
  14.                   Thread.Sleep(100); 
  15.                   Console.WriteLine($"hello, task2的線程ID為{ Thread.CurrentThread.ManagedThreadId}"); 
  16.               }); 
  17.  
  18.             //3.Task.Run(Action action)將任務放在線程池隊列,返回并啟動一個Task 
  19.             Task task3 = Task.Run(() => 
  20.               { 
  21.                   Thread.Sleep(100); 
  22.                   Console.WriteLine($"hello, task3的線程ID為{ Thread.CurrentThread.ManagedThreadId}"); 
  23.               }); 
  24.             Console.WriteLine("執行主線程!"); 
  25.             Console.ReadKey(); 
  26.         } 

執行結果如下:

我們看到先打印"執行主線程",然后再打印各個任務,說明了Task不會阻塞主線程。上邊的栗子Task都沒有返回值,我們也可以創建有返回值的Task ,用法和沒有返回值的基本一致,我們簡單修改一下上邊的栗子,代碼如下:

  1. static void Main(string[] args) 
  2.         { 
  3.             ////1.new方式實例化一個Task,需要通過Start方法啟動 
  4.             Task<string> task = new Task<string>(() => 
  5.             { 
  6.                 return $"hello, task1的ID為{Thread.CurrentThread.ManagedThreadId}"
  7.             }); 
  8.             task.Start(); 
  9.  
  10.             ////2.Task.Factory.StartNew(Func func)創建和啟動一個Task 
  11.            Task<string> task2 =Task.Factory.StartNew<string>(() => 
  12.             { 
  13.                 return $"hello, task2的ID為{ Thread.CurrentThread.ManagedThreadId}"
  14.             }); 
  15.  
  16.             ////3.Task.Run(Func func)將任務放在線程池隊列,返回并啟動一個Task 
  17.            Task<string> task3= Task.Run<string>(() => 
  18.             { 
  19.                 return $"hello, task3的ID為{ Thread.CurrentThread.ManagedThreadId}"
  20.             }); 
  21.  
  22.             Console.WriteLine("執行主線程!"); 
  23.             Console.WriteLine(task.Result); 
  24.             Console.WriteLine(task2.Result); 
  25.             Console.WriteLine(task3.Result); 
  26.             Console.ReadKey(); 
  27.         } 

注意task.Resut獲取結果時會阻塞線程,即如果task沒有執行完成,會等待task執行完成獲取到Result,然后再執行后邊的代碼,程序運行結果如下:

上邊的所有栗子中Task的執行都是異步的,不會阻塞主線程。有些場景下我們想讓Task同步執行怎么辦呢?Task提供了 task.RunSynchronously()用于同步執行Task任務,代碼如下:

  1. static void Main(string[] args) 
  2.         { 
  3.             Task task = new Task(() => 
  4.             { 
  5.                 Thread.Sleep(100); 
  6.                 Console.WriteLine("執行Task結束!"); 
  7.             }); 
  8.             //同步執行,task會阻塞主線程 
  9.             task.RunSynchronously(); 
  10.             Console.WriteLine("執行主線程結束!"); 
  11.             Console.ReadKey(); 
  12.         } 

執行結果如下:

2 Task的阻塞方法(Wait/WaitAll/WaitAny)

1 Thread阻塞線程的方法

使用Thread時,我們知道用thread.Join()方法即可阻塞主線程。看一個例子:

  1. static void Main(string[] args) 
  2.         { 
  3.             Thread th1 = new Thread(() => { 
  4.                 Thread.Sleep(500); 
  5.                 Console.WriteLine("線程1執行完畢!"); 
  6.             }); 
  7.             th1.Start(); 
  8.             Thread th2 = new Thread(() => { 
  9.                 Thread.Sleep(1000); 
  10.                 Console.WriteLine("線程2執行完畢!"); 
  11.             }); 
  12.             th2.Start(); 
  13.             //阻塞主線程 
  14.             th1.Join(); 
  15.             th2.Join(); 
  16.             Console.WriteLine("主線程執行完畢!"); 
  17.             Console.ReadKey(); 
  18.         } 

如果注釋掉兩個Join,執行結果是:先打印【主線程執行完畢】,而添加兩個Join方法后執行結果如下,實現了線程阻塞:

2 Task的Wait/WaitAny/WaitAll方法

Thread的Join方法可以阻塞調用線程,但是有一些弊端:①如果我們要實現很多線程的阻塞時,每個線程都要調用一次Join方法;②如果我們想讓所有的線程執行完畢(或者任一線程執行完畢)時,立即解除阻塞,使用Join方法不容易實現。Task提供了 Wait/WaitAny/WaitAll 方法,可以更方便地控制線程阻塞。

task.Wait() 表示等待task執行完畢,功能類似于thead.Join(); Task.WaitAll(Task[] tasks) 表示只有所有的task都執行完成了再解除阻塞; Task.WaitAny(Task[] tasks) 表示只要有一個task執行完畢就解除阻塞,看一個栗子:

  1. static void Main(string[] args) 
  2.         { 
  3.             Task task1 = new Task(() => { 
  4.                 Thread.Sleep(500); 
  5.                 Console.WriteLine("線程1執行完畢!"); 
  6.             }); 
  7.             task1.Start(); 
  8.             Task task2 = new Task(() => { 
  9.                 Thread.Sleep(1000); 
  10.                 Console.WriteLine("線程2執行完畢!"); 
  11.             }); 
  12.             task2.Start(); 
  13.             //阻塞主線程。task1,task2都執行完畢再執行主線程 
  14.        //執行【task1.Wait();task2.Wait();】可以實現相同功能 
  15.             Task.WaitAll(new Task[]{ task1,task2}); 
  16.             Console.WriteLine("主線程執行完畢!"); 
  17.             Console.ReadKey(); 
  18.         } 

執行結果如下:

如果將栗子中的WaitAll換成WaitAny,那么任一task執行完畢就會解除線程阻塞,執行結果是:先打印【線程1執行完畢】,然后打印【主線程執行完畢】,最后打印【線程2執行完畢】

3 Task的延續操作(WhenAny/WhenAll/ContinueWith)

上邊的Wait/WaitAny/WaitAll方法返回值為void,這些方法單純的實現阻塞線程。我們現在想讓所有task執行完畢(或者任一task執行完畢)后,開始執行后續操作,怎么實現呢?這時就可以用到WhenAny/WhenAll方法了,這些方法執行完成返回一個task實例。 task.WhenAll(Task[] tasks) 表示所有的task都執行完畢后再去執行后續的操作, task.WhenAny(Task[] tasks) 表示任一task執行完畢后就開始執行后續操作。看一個栗子:

 

  1. static void Main(string[] args) 
  2.         { 
  3.             Task task1 = new Task(() => { 
  4.                 Thread.Sleep(500); 
  5.                 Console.WriteLine("線程1執行完畢!"); 
  6.             }); 
  7.             task1.Start(); 
  8.             Task task2 = new Task(() => { 
  9.                 Thread.Sleep(1000); 
  10.                 Console.WriteLine("線程2執行完畢!"); 
  11.             }); 
  12.             task2.Start(); 
  13.             //task1,task2執行完了后執行后續操作 
  14.             Task.WhenAll(task1, task2).ContinueWith((t) => { 
  15.                 Thread.Sleep(100); 
  16.                 Console.WriteLine("執行后續操作完畢!"); 
  17.             }); 
  18.  
  19.             Console.WriteLine("主線程執行完畢!"); 
  20.             Console.ReadKey(); 
  21.         } 

執行結果如下,我們看到WhenAll/WhenAny方法不會阻塞主線程,當使用WhenAll方法時所有的task都執行完畢才會執行后續操作;如果把栗子中的WhenAll替換成WhenAny,則只要有一個線程執行完畢就會開始執行后續操作,這里不再演示。

上邊的栗子也可以通過 Task.Factory.ContinueWhenAll(Task[] tasks, Action continuationAction) 和 Task.Factory.ContinueWhenAny(Task[] tasks, Action continuationAction) 來實現 ,修改上邊栗子代碼如下,執行結果不變。

  1. static void Main(string[] args) 
  2.         { 
  3.             Task task1 = new Task(() => { 
  4.                 Thread.Sleep(500); 
  5.                 Console.WriteLine("線程1執行完畢!"); 
  6.             }); 
  7.             task1.Start(); 
  8.             Task task2 = new Task(() => { 
  9.                 Thread.Sleep(1000); 
  10.                 Console.WriteLine("線程2執行完畢!"); 
  11.             }); 
  12.             task2.Start(); 
  13.             //通過TaskFactroy實現 
  14.             Task.Factory.ContinueWhenAll(new Task[] { task1, task2 }, (t) => 
  15.             { 
  16.                 Thread.Sleep(100); 
  17.                 Console.WriteLine("執行后續操作"); 
  18.             }); 
  19.  
  20.             Console.WriteLine("主線程執行完畢!"); 
  21.             Console.ReadKey(); 
  22.         } 

4 Task的任務取消(CancellationTokenSource)

1 Thread取消任務執行

在Task前我們執行任務采用的是Thread,Thread怎么取消任務呢?一般流程是:設置一個變量來控制任務是否停止,如設置一個變量isStop,然后線程輪詢查看isStop,如果isStop為true就停止,代碼如下:

  1. static void Main(string[] args) 
  2.         { 
  3.             bool isStop = false
  4.             int index = 0; 
  5.             //開啟一個線程執行任務 
  6.             Thread th1 = new Thread(() => 
  7.               { 
  8.                   while (!isStop) 
  9.                   { 
  10.                       Thread.Sleep(1000); 
  11.                       Console.WriteLine($"第{++index}次執行,線程運行中..."); 
  12.                   } 
  13.               }); 
  14.             th1.Start(); 
  15.             //五秒后取消任務執行 
  16.             Thread.Sleep(5000); 
  17.             isStop = true
  18.             Console.ReadKey(); 
  19.         } 

2 Task取消任務執行

Task中有一個專門的類 CancellationTokenSource 來取消任務執行,還是使用上邊的例子,我們修改代碼如下,程序運行的效果不變。

  1. static void Main(string[] args) 
  2.         { 
  3.             CancellationTokenSource source = new CancellationTokenSource(); 
  4.             int index = 0; 
  5.             //開啟一個task執行任務 
  6.             Task task1 = new Task(() => 
  7.               { 
  8.                   while (!source.IsCancellationRequested) 
  9.                   { 
  10.                       Thread.Sleep(1000); 
  11.                       Console.WriteLine($"第{++index}次執行,線程運行中..."); 
  12.                   } 
  13.               }); 
  14.             task1.Start(); 
  15.             //五秒后取消任務執行 
  16.             Thread.Sleep(5000); 
  17.             //source.Cancel()方法請求取消任務,IsCancellationRequested會變成true 
  18.             source.Cancel(); 
  19.             Console.ReadKey(); 
  20.         } 

CancellationTokenSource的功能不僅僅是取消任務執行,我們可以使用 source.CancelAfter(5000) 實現5秒后自動取消任務,也可以通過 source.Token.Register(Action action) 注冊取消任務觸發的回調函數,即任務被取消時注冊的action會被執行。看一個栗子:

  1. static void Main(string[] args) 
  2.         { 
  3.             CancellationTokenSource source = new CancellationTokenSource(); 
  4.             //注冊任務取消的事件 
  5.             source.Token.Register(() => 
  6.             { 
  7.                 Console.WriteLine("任務被取消后執行xx操作!"); 
  8.             }); 
  9.  
  10.             int index = 0; 
  11.             //開啟一個task執行任務 
  12.             Task task1 = new Task(() => 
  13.               { 
  14.                   while (!source.IsCancellationRequested) 
  15.                   { 
  16.                       Thread.Sleep(1000); 
  17.                       Console.WriteLine($"第{++index}次執行,線程運行中..."); 
  18.                   } 
  19.               }); 
  20.             task1.Start(); 
  21.             //延時取消,效果等同于Thread.Sleep(5000);source.Cancel(); 
  22.             source.CancelAfter(5000); 
  23.             Console.ReadKey(); 
  24.         } 

執行結果如下,第5次執行在取消回調后打印,這是因為,執行取消的時候第5次任務已經通過了while()判斷,任務已經執行中了:

最后看上一篇跨線程的栗子,點擊按鈕啟動一個任務,給tetxtbox賦值,我們把Thread改成Task,代碼如下:

  1. public partial class Form1 : Form 
  2.     { 
  3.         public Form1() 
  4.         { 
  5.             InitializeComponent(); 
  6.         } 
  7.  
  8.         private void mySetValueBtn_Click(object sender, EventArgs e) 
  9.         { 
  10.             Task.Run(() => 
  11.             { 
  12.                 Action<int> setValue = (i) => { myTxtbox.Text = i.ToString(); }; 
  13.                 for (int i = 0; i < 1000000; i++) 
  14.                 { 
  15.                     myTxtbox.Invoke(setValue,i); 
  16.                 } 
  17.             }); 
  18.         } 
  19.     } 

運行界面如下,賦值的task不會阻塞UI線程:

回到頂部

三、異步方法(async/await)

在C#5.0中出現的 async和await ,讓異步編程變得更簡單。我們看一個獲取文件內容的栗子:

  1. class Program 
  2.     { 
  3.         static void Main(string[] args) 
  4.         { 
  5.             string content = GetContentAsync(Environment.CurrentDirectory + @"/test.txt").Result; 
  6.             //調用同步方法 
  7.             //string content = GetContent(Environment.CurrentDirectory + @"/test.txt"); 
  8.             Console.WriteLine(content); 
  9.             Console.ReadKey(); 
  10.         } 
  11.         //異步讀取文件內容 
  12.         async static Task<string> GetContentAsync(string filename) 
  13.         { 
  14.              
  15.             FileStream fs = new FileStream(filename, FileMode.Open); 
  16.             var bytes = new byte[fs.Length]; 
  17.             //ReadAync方法異步讀取內容,不阻塞線程 
  18.             Console.WriteLine("開始讀取文件"); 
  19.             int len = await fs.ReadAsync(bytes, 0, bytes.Length); 
  20.             string result = Encoding.UTF8.GetString(bytes); 
  21.             return result; 
  22.         } 
  23.         //同步讀取文件內容 
  24.         static string GetContent(string filename) 
  25.         { 
  26.             FileStream fs = new FileStream(filename, FileMode.Open); 
  27.             var bytes = new byte[fs.Length]; 
  28.             //Read方法同步讀取內容,阻塞線程 
  29.             int len =  fs.Read(bytes, 0, bytes.Length); 
  30.             string result = Encoding.UTF8.GetString(bytes); 
  31.             return result; 
  32.         } 
  33.     } 

test.txt內容是【hello world!】執行結果為:

上邊的栗子也寫出了同步讀取的方式,將main函數中的注釋去掉即可同步讀取文件內容。我們可以看到異步讀取代碼和同步讀取代碼基本一致。async/await讓異步編碼變得更簡單,我們可以像寫同步代碼一樣去寫異步代碼。注意一個小問題:異步方法中方法簽名返回值為Task,代碼中的返回值為T。上邊栗子中GetContentAsync的簽名返回值為Task,而代碼中返回值為string。牢記這一細節對我們分析異步代碼很有幫助。

異步方法簽名的返回值有以下三種:

① Task:如果調用方法想通過調用異步方法獲取一個T類型的返回值,那么簽名必須為Task ;

② Task:如果調用方法不想通過異步方法獲取一個值,僅僅想追蹤異步方法的執行狀態,那么我們可以設置異步方法簽名的返回值為Task;

③ void:如果調用方法僅僅只是調用一下異步方法,不和異步方法做其他交互,我們可以設置異步方法簽名的返回值為void,這種形式也叫做“調用并忘記”。

小結:到這里Task,async/await的簡單使用已經基本結束了,一些高級特性等到工作遇到了再去研究。通過上邊的介紹,我們知道async/await是基于Task的,而Task是對ThreadPool的封裝改進,主要是為了更有效的控制線程池中的線程(ThreadPool中的線程,我們很難通過代碼控制其執行順序,任務延續和取消等等);ThreadPool基于Thread的,主要目的是減少Thread創建數量和管理Thread的成本。async/await Task是C#中更先進的,也是微軟大力推廣的特性,我們在開發中可以嘗試使用Task來替代Thread/ThreadPool,處理本地IO和網絡IO任務是盡量使用async/await來提高任務執行效率。

本文轉載自微信公眾號「CSharp編程大全」,可以通過以下二維碼關注。轉載本文請聯系CSharp編程大全公眾號。

 

責任編輯:武曉燕 來源: CSharp編程大全
相關推薦

2024-06-25 08:33:48

2024-09-29 16:22:18

多線程TaskC#

2024-09-27 19:42:09

工具C#Task?

2013-05-16 10:33:11

C#C# 5.0Async

2009-08-21 11:31:59

異步和多線程的區別

2024-05-13 11:36:56

C#線程

2014-07-15 10:08:42

異步編程In .NET

2024-12-23 09:09:54

2009-08-20 18:11:08

C#異步委托

2010-12-22 10:21:17

C#基礎

2023-10-08 10:21:11

JavaScriptAsync

2024-10-07 08:28:03

WPFUI應用程序

2024-12-23 06:20:00

2024-03-06 13:23:56

Task.RunC#異步陷阱

2009-08-17 16:49:46

C#多線程控制

2024-05-16 11:04:06

C#異步編程編程

2023-06-13 13:39:00

多線程異步編程

2009-03-12 10:52:43

Java線程多線程

2024-12-23 08:00:45

2017-08-02 14:17:08

前端asyncawait
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美一区二区三区视频在线观看 | 国产精品一区二区三区四区五区 | 美女爽到呻吟久久久久 | 国产亚洲精品美女久久久久久久久久 | 黄a网| 国产日韩欧美在线观看 | 91精品久久久久久久久 | 中文字幕在线免费 | 欧美久久一区二区三区 | 精品久久精品 | 久久久久久久久久久久久久av | 欧美久操网 | www.婷婷亚洲基地 | 欧美成人自拍 | 色噜噜亚洲男人的天堂 | 成人a视频在线观看 | 中文av在线播放 | 欧美激情视频一区二区三区在线播放 | 国产一区二区三区欧美 | 亚洲嫩草 | 日本精品免费 | 中文字幕一区二区三区精彩视频 | 91国产精品在线 | 欧美日韩亚洲二区 | 亚洲三级免费看 | 精品免费国产一区二区三区四区介绍 | 亚洲香蕉在线视频 | 国产视频三区 | 精品国产黄a∨片高清在线 www.一级片 国产欧美日韩综合精品一区二区 | 国产成人亚洲精品 | 大乳boobs巨大吃奶挤奶 | 亚洲国产精品一区二区久久 | 精品久久久久一区二区国产 | 国产免费一区二区 | 中文字幕精品视频 | 欧美久久一区二区三区 | 丁香综合 | 久久一二区 | 99热这里只有精品8 激情毛片 | 四虎影视免费观看 | h视频免费在线观看 |