C#中的异步陷阱
本文主要介紹異步編程中,常見的異步陷阱:
1、Async沒有異步運行
我們來看下面代碼,猜測他是如何打印出下面的三個字符串:
/*Gotcha #1: Async does not run asynchronously*/ static void Main(string[] args) {Console.WriteLine("begin" + "【" + DateTime.Now.ToString() + "】");var child = Gotcha1.WorkThenWait();Console.WriteLine("started" + "【" + DateTime.Now.ToString() + "】");child.Wait();Console.WriteLine("completed" + "【" + DateTime.Now.ToString() + "】");Console.ReadKey(); } public static async Task WorkThenWait() {Thread.Sleep(5000);Console.WriteLine("work" + "【" + DateTime.Now.ToString() + "】");await Task.Delay(10000); }看這段代碼,如果你猜想,他會按順序打印出“begin“,”started”,“work”,“completed”,那樣的話,你就錯了。這段代碼會輸出“begin“,“work”,“started”,“completed”。
可以看出來,本段代碼本來的猜想是,由于WorkThenWait()方法中包含繁重耗時的任務(這里為Thread.Sleep(5000)),所以打算讓他異步執行這個方法,但是,可以看出問題出在await關鍵字用在了該段繁重耗時任務之后,所以后面的child.Wait()方法只是等待了Task.Delay(10000)的執行。
執行結果如下:
?2、忽略結果
我們來看下面代碼,猜測他是如何執行的,是否會等待:
/*Gotcha #2: Ignoring results*/ static void Main(string[] args) {Gotcha2.Handler();Console.ReadKey(); } public static async Task Handler() {Console.WriteLine("Before");Task.Delay(5000);Console.WriteLine("After"); }看這段代碼,你是否期待它會打印“Before”,等待5秒之后,再打印“After”?當然,又錯了。他會立即打印兩條字符串,直接沒有任何等待。問題出在,雖然Task.Delay返回了Task返回值,但是我們忘記了使用await關鍵字去等待直到他完成。
執行結果如下:
?3、Async void方法
我們來看下面代碼,判斷下是否能成功打印出異常信息:
/*Gotcha #3: Async void methods*/ static void Main(string[] args) {Gotcha3.CallThrowExceptionAsync();Console.ReadKey(); }private static async void ThrowExceptionAsync() {throw new InvalidOperationException(); }public static void CallThrowExceptionAsync() {try{ThrowExceptionAsync();}catch (Exception){Console.WriteLine("Failed");} }你是否覺得這段代碼會打印“Failed”?當然,這個異常沒有被捕獲到,因為ThrowExceptionAsync方法開始執行并立即返回(這個異常發生在background thread內部)。問題根源在于:對于例如 async void Foo(){...},這種方法,C#編譯器將生成一個返回void的方法,然后它會在后臺創建一個task并執行。這意味著你無法知道這項工作實際何時發生。
?4、Async void lambda函數
如果你將異步lambda函數作為委托傳遞給某個方法時。在這種情況下,C#編譯器將從委托類型推斷方法的類型。如果使用Action delegate,則編譯器生成async void function(這種后臺啟動線程工作并返回void)。如果使用Func<Task> delegate,編譯器講生成返回Task的function。
我們來看下面代碼,判斷這段代碼是在5秒之后執行完(等待所有的Task完成sleeping),或者它立即完成了?
/*Gotcha #4: Async void lambda functions*/ static void Main(string[] args) {Gotcha4.Test();Console.WriteLine("ok"); }public static void Test() {Parallel.For(0, 10, async i => {await Task.Delay(5000);}); }當然,直接看For無法判斷出他是否等待,我們借助小工具看下如下代碼的源碼:
public void TestMethod() {Parallel.For(0, 10, async i => {await Task.Delay(5000);}); } // AsyncGotchasLib.Class1 public void TestMethod() {int arg_23_0 = 0;int arg_23_1 = 10;Action<int> arg_23_2;if ((arg_23_2 = Class1.<>c.<>9__0_0) == null){arg_23_2 = (Class1.<>c.<>9__0_0 = new Action<int>(Class1.<>c.<>9.<TestMethod>b__0_0));}Parallel.For(arg_23_0, arg_23_1, arg_23_2); }可以看到For中,lambda函數最終編譯為了Action Delegate,因此這段代碼不會等待5秒,會立即執行完成,它的等待會在后臺線程執行。
?5、嵌套任務
看下面代碼,問題是,下面兩個print直接會不會等待5秒:
/*Gotcha #5: Nesting of tasks*/ static void Main(string[] args) {Gotcha5.Test();Console.ReadKey(); }public async static void Test() {Console.WriteLine("Before");await Task.Factory.StartNew(async () => { await Task.Delay(5000); Console.WriteLine("后臺線程等待5秒后"); });Console.WriteLine("After"); }同樣,相當意外,并沒有在兩個打印(“Before”,“After”)之間等待。為什么?StratNew 方法接受一個委托,并返回一個Task<T>,其中T表示又delegate返回的類型。在這個方法中,這個delegate返回Task,因此,我們獲得的結果為Task<Task>。使用await關鍵字僅僅等待外部Task的完成(它將立即返回內部task),這代表內部task將被忽略,內部task將在后臺線程執行。
轉載于:https://www.cnblogs.com/SimplePerson/p/7395513.html
總結
- 上一篇: 【shell】shuf命令,随机排序
- 下一篇: 原创:E325: ATTENTION v