C#多線程系列之a(chǎn)sync和await用法詳解
async和await
async
微軟文檔:使用 async
修飾符可將方法、lambda 表達(dá)式或匿名方法指定為異步。
使用 async 修飾的方法,稱為異步方法。
例如:
為了命名規(guī)范,使用 async 修飾的方法,需要在方法名稱后面加上 Async
。
public async Task<int> TestAsync() { // . . . . }
Lambda :
static void Main() { Thread thread = new Thread(async () => { await Task.Delay(0); }); } public static async Task<int> TestAsync() => 666;
await
微軟文檔:await
運(yùn)算符暫停對(duì)其所屬的 async
方法的求值,直到其操作數(shù)表示的異步操作完成。
異步操作完成后,await
運(yùn)算符將返回操作的結(jié)果(如果有)。
好的,到此為止,async 和 await ,在官方文檔中的說(shuō)明,就這么多。
從以往知識(shí)推導(dǎo)
這里,你會(huì)跟筆者從以往文章中學(xué)習(xí)到的知識(shí),去推導(dǎo),去理解 async 和 await 這兩個(gè)關(guān)鍵字是如何使用的,又應(yīng)該怎么合理使用。
這里我們不參考文檔和書籍的資料,不要看文檔和書籍中的示例,我們要一步步來(lái)從任務(wù)(Task)中的同步異步開(kāi)始,慢慢摸索。去分析 async 和 await 兩個(gè)關(guān)鍵字給我們的異步編程帶來(lái)了什么樣的便利。
創(chuàng)建異步任務(wù)
場(chǎng)景:周六日放假了,可以打王者(一種游戲),但是昨天的衣服還沒(méi)有洗;于是用洗衣機(jī)洗衣服,清洗期間,開(kāi)一局王者(一種游戲)。
我們可以編寫一個(gè)方法如下:
static void Main() { Console.WriteLine("準(zhǔn)備洗衣服"); // 創(chuàng)建一個(gè)洗衣服的任務(wù) Task<int> task = new Task<int>(() => { // 模擬洗衣服的時(shí)間 int time = new Random().Next(2, 6); Thread.Sleep(TimeSpan.FromSeconds(time)); return time; }); Console.WriteLine("開(kāi)始洗衣服"); // 讓洗衣機(jī)洗衣服 task.Start(); Console.WriteLine("我去打王者,讓洗衣機(jī)洗衣服"); // 打王者 Thread.Sleep(TimeSpan.FromSeconds(4)); Console.WriteLine("打完王者了,衣服洗完了嘛?"); Console.WriteLine(task.IsCompleted); if (task.IsCompleted) Console.WriteLine("洗衣服花的時(shí)間:" + task.Result); else { Console.WriteLine("在等洗衣機(jī)洗完衣服"); task.Wait(); Console.WriteLine("洗衣服花的時(shí)間:" + task.Result); } Console.WriteLine("洗完了,撈出衣服,曬衣服,繼續(xù)打王者去"); }
創(chuàng)建異步任務(wù)并返回Task
上面的示例,雖然說(shuō),異步完成了一個(gè)任務(wù),但是這樣,將代碼都放到 Main ,可讀性十分差,還要其它什么規(guī)范之類的,不允許我們寫這樣的垃圾代碼。于是我們將洗衣服這個(gè)任務(wù),封裝到一個(gè)方法中,然后返回 Task 即可。
在 Program 類中,加入如下一個(gè)方法,這個(gè)方法用于執(zhí)行異步任務(wù),并且返回 Task 對(duì)象。
/// <summary> /// 執(zhí)行一個(gè)任務(wù) /// </summary> /// <returns></returns> public static Task<int> TestAsync() { Task<int> task = new Task<int>(() => { // 模擬洗衣服的時(shí)間 int time = new Random().Next(2, 6); Thread.Sleep(TimeSpan.FromSeconds(time)); return time; }); task.Start(); return task; }
Main 方法中,改成
static void Main() { Console.WriteLine("準(zhǔn)備洗衣服"); // 創(chuàng)建一個(gè)洗衣服的任務(wù) Task<int> task = TestAsync(); ... ...
但是,兩者差別還是不大。
異步改同步
我們創(chuàng)建了異步方法,去執(zhí)行一個(gè)洗衣服的任務(wù);當(dāng)打完游戲后,需要檢查任務(wù)是否完成,然后才能進(jìn)行下一步操作,這時(shí)候就出現(xiàn)了 同步
。為了保持同步和獲得執(zhí)行結(jié)果,我們使用了 .Wait()
、.Result
。
這里我們嘗試將上面的操作轉(zhuǎn)為同步,并且獲得執(zhí)行結(jié)果。
class Program { static void Main() { int time = Test(); // ... ... } /// <summary> /// 執(zhí)行一個(gè)任務(wù) /// </summary> /// <returns></returns> public static int Test() { Task<int> task = new Task<int>(() => { // 模擬洗衣服的時(shí)間 int time = new Random().Next(2, 6); Thread.Sleep(TimeSpan.FromSeconds(time)); return time; }); task.Start(); return task.Result; } }
說(shuō)說(shuō) await Task
Task
和 Task<TResult>
,前者是一個(gè)沒(méi)有返回結(jié)果的任務(wù),后者是有返回結(jié)果的任務(wù)。前面的文章中已經(jīng)使用過(guò)大量的示例,這里我們使用 await ,去完成一些完全相同的功能。
Task
:
public static void T1() { Task task = new Task(() => { }); task.Wait(); }
public static async void T2() { Task task = new Task(() => { }); await task; }
說(shuō)明,await 可以讓程序等待任務(wù)完成。
Task<TResult>
:
public void T3() { // 獲取 Task 任務(wù)對(duì)象,后面的邏輯過(guò)程可以弄成異步 Task<int> task = TestAsync(); // 任務(wù)是異步在執(zhí)行,我不理會(huì)他 // 這里可以處理其它事情,處理完畢后,再獲取執(zhí)行結(jié)果 // 這就是異步 Console.WriteLine(task.Result); }
public async void T4() { // 使用 await 關(guān)鍵字,代表等待執(zhí)行完成,同步 int time = await TestAsync(); Console.WriteLine(time); }
說(shuō)明:await 可以讓程序等待任務(wù)執(zhí)行完成,并且獲得執(zhí)行結(jié)果。
看到?jīng)]有。。。await 關(guān)鍵字,作用是讓你等,是同步的,壓根不是直接讓你的任務(wù)變成異步后臺(tái)執(zhí)行的。
那為啥提到 async 、await,都是說(shuō)跟異步有關(guān)?不急,后面解釋。
說(shuō)說(shuō) async Task<TResult>
async Task<TResult>
修飾一個(gè)方法,那么這個(gè)方法要返回 await Task<TResult>
的結(jié)果。
兩種同步方式示例對(duì)比:
public static int Test() { Task<int> task = new Task<int>(() => { // 模擬洗衣服的時(shí)間 int time = new Random().Next(2, 6); Thread.Sleep(TimeSpan.FromSeconds(time)); return time; }); task.Start(); return task.Result; }
public static async Task<int> TestAsync() { Task<int> task = new Task<int>(() => { // 模擬洗衣服的時(shí)間 int time = new Random().Next(2, 6); Thread.Sleep(TimeSpan.FromSeconds(time)); return time; }); task.Start(); int time = await task; return time; }
同步異步?
問(wèn):async 和 await 不是跟異步方法有關(guān)嘛,為啥前面的示例使用了 await ,全部變成同步了?
問(wèn):使用 async 和 await 的方法,執(zhí)行過(guò)程到底是同步還是異步?
答:同步異步都行,要同步還是異步,全掌握在你的手上。
- 你使用 await 去調(diào)用一個(gè)異步方法,其執(zhí)行過(guò)程就是同步。
- 你獲取異步方法返回的 Task,就是異步。
最近筆者收到一些提問(wèn),有些讀者,使用 async 和 await 去編寫業(yè)務(wù),想著是異步,可以提升性能,實(shí)際結(jié)果還是同步,性能一點(diǎn)沒(méi)有提升。通過(guò)下面的示例,你會(huì)馬上理解應(yīng)該怎么用。
首先,在不使用 async 和 await 關(guān)鍵字的情況下,我們來(lái)編寫兩個(gè)方法,分別實(shí)現(xiàn)同步和異步的功能,兩個(gè)方法執(zhí)行的結(jié)果是一致的。
/// <summary> /// 同步 /// </summary> /// <returns></returns> public static int Test() { Task<int> task = new Task<int>(() => { return 666; }); task.Start(); return task.Result; } /// <summary> /// 異步 /// </summary> /// <returns></returns> public static Task<int> TestAsync() { Task<int> task = new Task<int>(() => { return 666; }); task.Start(); return task; }
能不能將兩個(gè)方法合并在一起呢?想同步就同步,想異步就異步,這樣就不需要寫兩個(gè)方法了!
是可以的!通過(guò) async 和 await 關(guān)鍵字,可以輕松實(shí)現(xiàn)!
合并后,代碼如下:
/// <summary> /// 可異步可同步 /// </summary> /// <returns></returns> public static async Task<int> TestAsync() { Task<int> task = new Task<int>(() => { return 666; }); task.Start(); return await task; }
合并后,我們又應(yīng)該怎么在調(diào)用的時(shí)候,實(shí)現(xiàn)同步和異步呢?
筆者這里給出兩個(gè)示例:
// await 使得任務(wù)同步 public async void T1() { // 使用 await 關(guān)鍵字,代表等待執(zhí)行完成,同步 int time = await TestAsync(); Console.WriteLine(time); } // 直接獲得返回的 Task,實(shí)現(xiàn)異步 public void T2() { // 獲取 Task 任務(wù)對(duì)象,后面的邏輯過(guò)程可以弄成異步 Task<int> task = TestAsync(); // 任務(wù)是異步在執(zhí)行,我不理會(huì)他 // 這里可以處理其它事情,處理完畢后,再獲取執(zhí)行結(jié)果 // 這就是異步 Console.WriteLine(task.Result); }
至此,理解為什么使用了 async
和 await
,執(zhí)行時(shí)還是同步了吧?
Task封裝異步任務(wù)
前面,我們都是使用了 new Task()
來(lái)創(chuàng)建任務(wù),而且微軟官網(wǎng)大多使用 Task.Run()
來(lái)編寫 async 和 await 的示例。
因此,我們可以修改前面的異步任務(wù),改成:
/// <summary> /// 可異步可同步 /// </summary> /// <returns></returns> public static async Task<int> TestAsync() { return await Task.Run<int>(() => { return 666; }); }
關(guān)于跳到 await 變異步
在百度學(xué)習(xí)異步的時(shí)候,往往會(huì)有作者說(shuō),進(jìn)入異步方法后,同步執(zhí)行代碼,碰到 await 后就是異步執(zhí)行。
當(dāng)然還有多種說(shuō)法。
我們已經(jīng)學(xué)習(xí)了這么多的任務(wù)(Task)知識(shí),這一點(diǎn)十分容易解釋。
因?yàn)槭褂昧?async 和 await 關(guān)鍵字,代碼最深處,必定會(huì)出現(xiàn) Task 這個(gè)東西,Task 這個(gè)東西本來(lái)就是異步。碰到 await 出現(xiàn)異步,不是因?yàn)?await 的作用,而是因?yàn)樽畹讓佑袀€(gè) Task。
為什么出現(xiàn)一層層的 await
這是相對(duì)于提供服務(wù)者來(lái)說(shuō)。因?yàn)槲乙峁┙涌诮o你使用,因此底層出現(xiàn) async、await 后,我會(huì)繼續(xù)保留方法是異步的(async),然后繼續(xù)封裝,這樣就有多層的調(diào)用結(jié)構(gòu),例如上一小節(jié)的圖。
但是如果來(lái)到了調(diào)用者這里,就不應(yīng)該還是使用 async 、await 去編寫方法,而是應(yīng)該按照實(shí)際情況同步或異步。
到此這篇關(guān)于C#多線程系列之a(chǎn)sync和await用法詳解的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#基礎(chǔ)學(xué)習(xí)系列之Attribute和反射詳解
大家在使用Attribute的時(shí)候大多需要用到反射,所以放在一起。下面這篇文章主要給大家介紹了關(guān)于C#基礎(chǔ)學(xué)習(xí)系列之Attribute和反射的相關(guān)資料,文中給出了詳細(xì)的示例代碼供大家參考學(xué)習(xí),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09淺析C#?AsyncLocal如何實(shí)現(xiàn)Thread間傳值
這篇文章主要是來(lái)和大家一起討論一下C#?AsyncLocal如何實(shí)現(xiàn)Thread間傳值,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01.net(c#)中的new關(guān)鍵字詳細(xì)介紹
在 C# 中,new 關(guān)鍵字可用作運(yùn)算符、修飾符或約束2013-10-10C# 兩種方式反編譯修改源碼(dnspy,ildasm & ilasm)
這篇文章主要介紹了C# 兩種方式反編譯修改源碼(dnspy,ildasm & ilasm),幫助大家更好的理解和使用c#語(yǔ)言,感興趣的朋友可以了解下2020-11-11C#窗口轉(zhuǎn)向方式(由一個(gè)窗口,跳轉(zhuǎn)到另一個(gè)窗口)
這篇文章主要介紹了C#窗口轉(zhuǎn)向方式(由一個(gè)窗口,跳轉(zhuǎn)到另一個(gè)窗口)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07