async 和 await的前世今生 (轉載)

async 和 await 出現在C# 5.0之后,給并行編程帶來了不少的方便,特別是當在MVC中的Action也變成async之后,有點開始什么都是async的味道了。但是這也給我們編程埋下了一些隱患,有時候可能會產生一些我們自己都不知道怎么產生的Bug,特別是如果連線程基礎沒有理解的情況下,更不知道如何去處理了。那今天我們就來好好看看這兩兄弟和他們的叔叔(Task)爺爺(Thread)們到底有什么區別和特點,本文將會對Thread 到 Task 再到 .NET 4.5的 async和 await,這三種方式下的并行編程作一個概括性的介紹包括:開啟線程,線程結果返回,線程中止,線程中的異常處理等。

內容索引

  • 創建線程
  • 線程池
  • 參數
  • 返回值
  • 共享數據
  • 線程安全
  • Semaphore
  • 異常處理
  • 一個小例子認識async & await
  • await的原形

創建

1
2
3
4
5
6
7
8
9
static?void?Main(){
????new?Thread(Go).Start();??// .NET 1.0開始就有的
????Task.Factory.StartNew(Go);?// .NET 4.0 引入了 TPL
????Task.Run(new?Action(Go));?// .NET 4.5 新增了一個Run的方法
}
public?static?void?Go(){
????Console.WriteLine("我是另一個線程");
}

  這里面需要注意的是,創建Thread的實例之后,需要手動調用它的Start方法將其啟動。但是對于Task來說,StartNew和Run的同時,既會創建新的線程,并且會立即啟動它。

線程池?

  線程的創建是比較占用資源的一件事情,.NET 為我們提供了線程池來幫助我們創建和管理線程。Task是默認會直接使用線程池,但是Thread不會。如果我們不使用Task,又想用線程池的話,可以使用ThreadPool類。

1
2
3
4
5
6
7
8
9
10
static?void?Main() {
????Console.WriteLine("我是主線程:Thread Id {0}", Thread.CurrentThread.ManagedThreadId);
????ThreadPool.QueueUserWorkItem(Go);
????Console.ReadLine();
}
public?static?void?Go(object?data) {
????Console.WriteLine("我是另一個線程:Thread Id {0}",Thread.CurrentThread.ManagedThreadId);
}

傳入參數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static?void?Main() {
????new?Thread(Go).Start("arg1");?// 沒有匿名委托之前,我們只能這樣傳入一個object的參數
????new?Thread(delegate(){??// 有了匿名委托之后...
????????GoGoGo("arg1",?"arg2",?"arg3");
????});
????new?Thread(() => {??// 當然,還有 Lambada
????????GoGoGo("arg1","arg2","arg3");
????}).Start();
????Task.Run(() =>{??// Task能這么靈活,也是因為有了Lambda呀。
????????GoGoGo("arg1",?"arg2",?"arg3");
????});
}
public?static?void?Go(object?name){
????// TODO
}
public?static?void?GoGoGo(string?arg1,?string?arg2,?string?arg3){
????// TODO
}

返回值

  Thead是不能返回值的,但是作為更高級的Task當然要彌補一下這個功能。

1
2
3
4
5
static?void?Main() {
????// GetDayOfThisWeek 運行在另外一個線程中
????var?dayName = Task.Run<string>(() => {?return?GetDayOfThisWeek(); });
????Console.WriteLine("今天是:{0}",dayName.Result);
}

共享數據

  上面說了參數和返回值,我們來看一下線程之間共享數據的問題。

1
2
3
4
5
6
7
8
9
10
11
12
private?static?bool?_isDone =?false;???
static?void?Main(){
????new?Thread(Done).Start();
????new?Thread(Done).Start();
}
static?void?Done(){
????if?(!_isDone) {
????????_isDone =?true;?// 第二個線程來的時候,就不會再執行了(也不是絕對的,取決于計算機的CPU數量以及當時的運行情況)
????????Console.WriteLine("Done");
????}
}

 

  線程之間可以通過static變量來共享數據。

線程安全

?  我們先把上面的代碼小小的調整一下,就知道什么是線程安全了。我們把Done方法中的兩句話對換了一下位置 。

1
2
3
4
5
6
7
8
9
10
11
12
13
private?static?bool?_isDone =?false;???
static?void?Main(){
????new?Thread(Done).Start();
????new?Thread(Done).Start();
????Console.ReadLine();
}
static?void?Done(){
????if?(!_isDone) {
???????Console.WriteLine("Done");?// 猜猜這里面會被執行幾次?
????????_isDone =?true;
????}
}

?

  上面這種情況不會一直發生,但是如果你運氣好的話,就會中獎了。因為第一個線程還沒有來得及把_isDone設置成true,第二個線程就進來了,而這不是我們想要的結果,在多個線程下,結果不是我們的預期結果,這就是線程不安全。

  要解決上面遇到的問題,我們就要用到鎖。鎖的類型有獨占鎖,互斥鎖,以及讀寫鎖等,我們這里就簡單演示一下獨占鎖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private?static?bool?_isDone =?false;
private?static?object?_lock =?new?object();
static?void?Main(){
????new?Thread(Done).Start();
????new?Thread(Done).Start();
????Console.ReadLine();
}
static?void?Done(){
????lock?(_lock){
????????if?(!_isDone){
????????????Console.WriteLine("Done");?// 猜猜這里面會被執行幾次?
????????????_isDone =?true;
????????}
????}
}

  再我們加上鎖之后,被鎖住的代碼在同一個時間內只允許一個線程訪問,其它的線程會被阻塞,只有等到這個鎖被釋放之后其它的線程才能執行被鎖住的代碼。

Semaphore 信號量

  我實在不知道這個單詞應該怎么翻譯,從官方的解釋來看,我們可以這樣理解。它可以控制對某一段代碼或者對某個資源訪問的線程的數量,超過這個數量之后,其它的線程就得等待,只有等現在有線程釋放了之后,下面的線程才能訪問。這個跟鎖有相似的功能,只不過不是獨占的,它允許一定數量的線程同時訪問。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static?SemaphoreSlim _sem =?new?SemaphoreSlim(3);????// 我們限制能同時訪問的線程數量是3
static?void?Main(){
????for?(int?i = 1; i <= 5; i++)?new?Thread(Enter).Start(i);
????Console.ReadLine();
}
static?void?Enter(object?id){
????Console.WriteLine(id +?" 開始排隊...");
????_sem.Wait();
????Console.WriteLine(id +?" 開始執行!");?????????
????Thread.Sleep(1000 * (int)id);??????????????
????Console.WriteLine(id +?" 執行完畢,離開!");?????
????_sem.Release();
}

?

  

在最開始的時候,前3個排隊之后就立即進入執行,但是4和5,只有等到有線程退出之后才可以執行。

異常處理

  其它線程的異常,主線程可以捕獲到么?

1
2
3
4
5
6
7
8
9
10
public?static?void?Main(){
????try{
????????new?Thread(Go).Start();
????}
????catch?(Exception ex){
????????// 其它線程里面的異常,我們這里面是捕獲不到的。
????????Console.WriteLine("Exception!");
????}
}
static?void?Go() {?throw?null; }

  那么升級了的Task呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public?static?void?Main(){
????try{
????????var?task = Task.Run(() => { Go(); });
????????task.Wait();??// 在調用了這句話之后,主線程才能捕獲task里面的異常
????????// 對于有返回值的Task, 我們接收了它的返回值就不需要再調用Wait方法了
????????// GetName 里面的異常我們也可以捕獲到
????????var?task2 = Task.Run(() => {?return?GetName(); });
????????var?name = task2.Result;
????}
????catch?(Exception ex){
????????Console.WriteLine("Exception!");
????}
}
static?void?Go() {?throw?null; }
static?string?GetName() {?throw?null; }

一個小例子認識async & await

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static?void?Main(string[] args){
????Test();?// 這個方法其實是多余的, 本來可以直接寫下面的方法
????// await GetName()?
????// 但是由于控制臺的入口方法不支持async,所有我們在入口方法里面不能 用 await
?????????????
????Console.WriteLine("Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId);
}
static?async Task Test(){
????// 方法打上async關鍵字,就可以用await調用同樣打上async的方法
????// await 后面的方法將在另外一個線程中執行
????await GetName();
}
static?async Task GetName(){
????// Delay 方法來自于.net 4.5
????await Task.Delay(1000);??// 返回值前面加 async 之后,方法里面就可以用await了
????Console.WriteLine("Current Thread Id :{0}", Thread.CurrentThread.ManagedThreadId);
????Console.WriteLine("In antoher thread.....");
}

await 的原形

??await后的的執行順序?

?

? ???感謝 locus的指正,?await 之后不會開啟新的線程(await 從來不會開啟新的線程),所以上面的圖是有一點問題的。

  await 不會開啟新的線程,當前線程會一直往下走直到遇到真正的Async方法(比如說HttpClient.GetStringAsync),這個方法的內部會用Task.Run或者Task.Factory.StartNew 去開啟線程。也就是如果方法不是.NET為我們提供的Async方法,我們需要自己創建Task,才會真正的去創建線程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static?void?Main(string[] args)
{
????Console.WriteLine("Main Thread Id: {0}\r\n", Thread.CurrentThread.ManagedThreadId);
????Test();
????Console.ReadLine();
}
static?async Task Test()
{
????Console.WriteLine("Before calling GetName, Thread Id: {0}\r\n", Thread.CurrentThread.ManagedThreadId);
????var?name = GetName();???//我們這里沒有用 await,所以下面的代碼可以繼續執行
????// 但是如果上面是 await GetName(),下面的代碼就不會立即執行,輸出結果就不一樣了。
????Console.WriteLine("End calling GetName.\r\n");
????Console.WriteLine("Get result from GetName: {0}", await name);
}
static?async Task<string> GetName()
{
????// 這里還是主線程
????Console.WriteLine("Before calling Task.Run, current thread Id is: {0}", Thread.CurrentThread.ManagedThreadId);
????return?await Task.Run(() =>
????{
????????Thread.Sleep(1000);
????????Console.WriteLine("'GetName' Thread Id: {0}", Thread.CurrentThread.ManagedThreadId);
????????return?"Jesse";
????});
}

  我們再來看一下那張圖:

  

  1. 進入主線程開始執行
  2. 調用async方法,返回一個Task,注意這個時候另外一個線程已經開始運行,也就是GetName里面的 Task?已經開始工作了
  3. 主線程繼續往下走
  4. 第3步和第4步是同時進行的,主線程并沒有掛起等待
  5. 如果另一個線程已經執行完畢,name.IsCompleted=true,主線程仍然不用掛起,直接拿結果就可以了。如果另一個線程還同有執行完畢, name.IsCompleted=false,那么主線程會掛起等待,直到返回結果為止。

只有async方法在調用前才能加await么?

1
2
3
4
5
6
7
8
9
10
11
12
13
static?void?Main(){
????Test();
????Console.ReadLine();
}
static?async?void?Test(){
????Task<string> task = Task.Run(() =>{
????????Thread.Sleep(5000);
????????return?"Hello World";
????});
????string?str = await task;??//5 秒之后才會執行這里
????Console.WriteLine(str);
}

  答案很明顯:await并不是針對于async的方法,而是針對async方法所返回給我們的Task,這也是為什么所有的async方法都必須返回給我們Task。所以我們同樣可以在Task前面也加上await關鍵字,這樣做實際上是告訴編譯器我需要等這個Task的返回值或者等這個Task執行完畢之后才能繼續往下走。

不用await關鍵字,如何確認Task執行完畢了?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static?void?Main(){
????var?task = Task.Run(() =>{
????????return?GetName();
????});
????task.GetAwaiter().OnCompleted(() =>{
????????// 2 秒之后才會執行這里
????????var?name = task.Result;
????????Console.WriteLine("My name is: "?+ name);
????});
????Console.WriteLine("主線程執行完畢");
????Console.ReadLine();
}
static?string?GetName(){
????Console.WriteLine("另外一個線程在獲取名稱");
????Thread.Sleep(2000);
????return?"Jesse";
}

Task.GetAwaiter()和await Task 的區別?

?

  • 加上await關鍵字之后,后面的代碼會被掛起等待,直到task執行完畢有返回值的時候才會繼續向下執行,這一段時間主線程會處于掛起狀態。
  • GetAwaiter方法會返回一個awaitable的對象(繼承了INotifyCompletion.OnCompleted方法)我們只是傳遞了一個委托進去,等task完成了就會執行這個委托,但是并不會影響主線程,下面的代碼會立即執行。這也是為什么我們結果里面第一句話會是 “主線程執行完畢”!

Task如何讓主線程掛起等待?

  上面的右邊是屬于沒有掛起主線程的情況,和我們的await仍然有一點差別,那么在獲取Task的結果前如何掛起主線程呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static?void?Main(){
????var?task = Task.Run(() =>{
????????return?GetName();
????});
????var?name = task.GetAwaiter().GetResult();
????Console.WriteLine("My name is:{0}",name);
????Console.WriteLine("主線程執行完畢");
????Console.ReadLine();
}
static?string?GetName(){
????Console.WriteLine("另外一個線程在獲取名稱");
????Thread.Sleep(2000);
????return?"Jesse";
}

  

Task.GetAwait()方法會給我們返回一個awaitable的對象,通過調用這個對象的GetResult方法就會掛起主線程,當然也不是所有的情況都會掛起。還記得我們Task的特性么? 在一開始的時候就啟動了另一個線程去執行這個Task,當我們調用它的結果的時候如果這個Task已經執行完畢,主線程是不用等待可以直接拿其結果的,如果沒有執行完畢那主線程就得掛起等待了。

await 實質是在調用awaitable對象的GetResult方法

1
2
3
4
5
6
7
8
9
10
11
12
13
static?async Task Test(){
????Task<string> task = Task.Run(() =>{
????????Console.WriteLine("另一個線程在運行!");??// 這句話只會被執行一次
????????Thread.Sleep(2000);
????????return?"Hello World";
????});
????// 這里主線程會掛起等待,直到task執行完畢我們拿到返回結果
????var?result = task.GetAwaiter().GetResult();?
????// 這里不會掛起等待,因為task已經執行完了,我們可以直接拿到結果
????var?result2 = await task;????
????Console.WriteLine(str);
}

到此為止,await就真相大白了,歡迎點評。Enjoy Coding! :)?

?

原文鏈接:?http://www.cnblogs.com/jesse2013/p/async-and-await.html

?

轉載于:https://www.cnblogs.com/miaosha5s/p/7799194.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/390874.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/390874.shtml
英文地址,請注明出處:http://en.pswp.cn/news/390874.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

項目案例:qq數據庫管理_2小時元項目:項目管理您的數據科學學習

項目案例:qq數據庫管理Many of us are struggling to prioritize our learning as a working professional or aspiring data scientist. We’re told that we need to learn so many things that at times it can be overwhelming. Recently, I’ve felt like there could be …

react 示例_2020年的React Cheatsheet(+真實示例)

react 示例Ive put together for you an entire visual cheatsheet of all of the concepts and skills you need to master React in 2020.我為您匯總了2020年掌握React所需的所有概念和技能的完整視覺摘要。 But dont let the label cheatsheet fool you. This is more than…

leetcode 993. 二叉樹的堂兄弟節點

在二叉樹中&#xff0c;根節點位于深度 0 處&#xff0c;每個深度為 k 的節點的子節點位于深度 k1 處。 如果二叉樹的兩個節點深度相同&#xff0c;但 父節點不同 &#xff0c;則它們是一對堂兄弟節點。 我們給出了具有唯一值的二叉樹的根節點 root &#xff0c;以及樹中兩個…

Java之Set集合的怪

工作中可能用Set比較少&#xff0c;但是如果用的時候&#xff0c;出的一些問題很讓人摸不著頭腦&#xff0c;然后我就看了一下Set的底層實現&#xff0c;大吃一驚。 ###看一個問題 Map map new HashMap();map.put(1,"a");map.put(12,"ab");map.put(123,&q…

為mysql數據庫建立索引

前些時候&#xff0c;一位頗高級的程序員居然問我什么叫做索引&#xff0c;令我感到十分的驚奇&#xff0c;我想這絕不會是滄海一粟&#xff0c;因為有成千上萬的開發者&#xff08;可能大部分是使用MySQL的&#xff09;都沒有受過有關數據庫的正規培訓&#xff0c;盡管他們都為…

查詢數據庫中有多少個數據表_您的數據中有多少汁?

查詢數據庫中有多少個數據表97%. That’s the percentage of data that sits unused by organizations according to Gartner, making up so-called “dark data”.97 &#xff05;。 根據Gartner的說法&#xff0c;這就是組織未使用的數據百分比&#xff0c;即所謂的“ 暗數據…

記錄一個Python鼠標自動模塊用法和selenium加載網頁插件的設置

寫爬蟲&#xff0c;或者網頁自動化&#xff0c;讓程序自動完成一些重復性的枯燥的網頁操作&#xff0c;是最常見的需求。能夠解放雙手&#xff0c;空出時間看看手機&#xff0c;或者學習別的東西&#xff0c;甚至還能幫朋友親戚減輕工作量。 然而&#xff0c;網頁自動化代碼編寫…

和css3實例教程_最好CSS和CSS3教程

和css3實例教程級聯樣式表(CSS) (Cascading Style Sheets (CSS)) CSS is an acronym for Cascading Style Sheets. It was first invented in 1996, and is now a standard feature of all major web browsers.CSS是層疊樣式表的縮寫。 它于1996年首次發明&#xff0c;現在已成…

leetcode 1442. 形成兩個異或相等數組的三元組數目(位運算)

給你一個整數數組 arr 。 現需要從數組中取三個下標 i、j 和 k &#xff0c;其中 (0 < i < j < k < arr.length) 。 a 和 b 定義如下&#xff1a; a arr[i] ^ arr[i 1] ^ … ^ arr[j - 1] b arr[j] ^ arr[j 1] ^ … ^ arr[k] 注意&#xff1a;^ 表示 按位異…

數據科學與大數據技術的案例_作為數據科學家解決問題的案例研究

數據科學與大數據技術的案例There are two myths about how data scientists solve problems: one is that the problem naturally exists, hence the challenge for a data scientist is to use an algorithm and put it into production. Another myth considers data scient…

AJAX, callback,promise and generator

AJAX with jQuery $.ajax({url:??,type:??,data:??,success: function(){??} //callback,error:function(jqXHR,textStatus,error){??} })think about what AJAX wants from human , AJAX asks questions : tell Me By Which Way You Want To Do Things : —— GET …

Spring-Boot + AOP實現多數據源動態切換

2019獨角獸企業重金招聘Python工程師標準>>> 最近在做保證金余額查詢優化&#xff0c;在項目啟動時候需要把余額全量加載到本地緩存&#xff0c;因為需要全量查詢所有騎手的保證金余額&#xff0c;為了不影響主數據庫的性能&#xff0c;考慮把這個查詢走從庫。所以涉…

css 幻燈片_如何使用HTML,CSS和JavaScript創建幻燈片

css 幻燈片A web slideshow is a sequence of images or text that consists of showing one element of the sequence in a certain time interval.網絡幻燈片是一系列圖像或文本&#xff0c;包括在一定時間間隔內顯示序列中的一個元素。 For this tutorial you can create a…

leetcode 1738. 找出第 K 大的異或坐標值

本文正在參加「Java主題月 - Java 刷題打卡」&#xff0c;詳情查看 活動鏈接 題目 給你一個二維矩陣 matrix 和一個整數 k &#xff0c;矩陣大小為 m x n 由非負整數組成。 矩陣中坐標 (a, b) 的 值 可由對所有滿足 0 < i < a < m 且 0 < j < b < n 的元素…

【數據庫】Oracle用戶、授權、角色管理

創建和刪除用戶是Oracle用戶管理中的常見操作&#xff0c;但這其中隱含了Oracle數據庫系統的系統權限與對象權限方面的知識。掌握還Oracle用戶的授權操作和原理&#xff0c;可以有效提升我們的工作效率。 Oracle數據庫的權限系統分為系統權限與對象權限。系統權限( Database Sy…

商業數據科學

數據科學 &#xff0c; 意見 (Data Science, Opinion) “There is a saying, ‘A jack of all trades and a master of none.’ When it comes to being a data scientist you need to be a bit like this, but perhaps a better saying would be, ‘A jack of all trades and …

為什么游戲開發者不玩游戲_什么是游戲開發?

為什么游戲開發者不玩游戲Game Development is the art of creating games and describes the design, development and release of a game. It may involve concept generation, design, build, test and release. While you create a game, it is important to think about t…

leetcode 692. 前K個高頻單詞

題目 給一非空的單詞列表&#xff0c;返回前 k 個出現次數最多的單詞。 返回的答案應該按單詞出現頻率由高到低排序。如果不同的單詞有相同出現頻率&#xff0c;按字母順序排序。 示例 1&#xff1a; 輸入: ["i", "love", "leetcode", "…

數據顯示,中國近一半的獨角獸企業由“BATJ”四巨頭投資

中國的互聯網行業越來越有被巨頭壟斷的趨勢。百度、阿里巴巴、騰訊、京東&#xff0c;這四大巨頭支撐起了中國近一半的獨角獸企業。CB Insights日前發表了題為“Nearly Half Of China’s Unicorns Backed By Baidu, Alibaba, Tencent, Or JD.com”的數據分析文章&#xff0c;列…

Java的Servlet、Filter、Interceptor、Listener

寫在前面&#xff1a; 使用Spring-Boot時&#xff0c;嵌入式Servlet容器可以通過掃描注解&#xff08;ServletComponentScan&#xff09;的方式注冊Servlet、Filter和Servlet規范的所有監聽器&#xff08;如HttpSessionListener監聽器&#xff09;。 Spring boot 的主 Servlet…