標籤:

c#中為什麼async方法里必須還要有await?

用async修飾的方法本身就是期望它為一個非同步方法,可是為什麼該方法內必須要有await?

舉個例子,有一個方法 double Factorial(int i),作用是計算i的階乘,非非同步的方法是

public double Factorial(int i)

{

double r=1.0;

do{r*=i}while(--i);

return r;

}

調用: var r = Factorial(i):

現在改成非同步時,public async Task& Factorial(i)里必須要用Task並且要await task,為何不能用以下形式:

public async double Factorial(int i)

{

double r=1.0;

do{r*=i}while(--i);

return r;

}

調用時 var r = await Factorial(i);

因為方法的聲明async和用await調用已經很明確了非同步,可是c#的async/await為什麼設計成在Factorial里再「多此一舉」(可能是有必要的,特打上引號)用Task, await Task,這是合理和必要的嗎?如果是的,為什麼呢?

換一種表述,既然用async聲明一個內部沒有await的方法是脫褲子放屁,那麼為什麼不增強async的作用而要保留這麼一個脫褲子放屁的使用方式。例如

async double Factorial,編譯時編譯成2個方法,一個是正常的double Factorial,另一個是供非同步調用的async_Factorial,當不使用await調用時選擇調用Factorial,await調用時調用async_Factorial,而這個async_Factorial裡面乾的事情就是將Factorial塞進Task里然後返回await task。


不是一般的混亂……

首先一個被標記為async的方法,可以沒有await調用,只不過會有編譯警告

這是很顯然的,不是說你把一個方法標記成async這個方法就成了非同步調用的方法了。async這個關鍵詞其實反而是可以省略的,這個關鍵詞存在的意義是為了向下兼容,為await提供上下文而已。

所以,一個async的方法裡面沒有await的調用,那等於是脫了褲子放屁,本質上只是把return xxx改成了retrurn Task.FromResult( xxx )而已,沒有任何變化。如果一個方法加上了async他就自動成為了非同步的調用,說明你連最根本的非同步是什麼都沒搞清楚。你所理解的那種所謂的非同步,直接用Task.Run就可以了。


如果沒有await關鍵字,你就阻止了「調用這個函數其實是想獲得一個Task&」的可能性。萬一我不想await呢?


async只是表明method中有非同步計算,具體在哪是await標識的。如果你有resharper的話,去掉method中的非同步(await)部分,會提示你async是多餘的會造成不必要的性能損失。

如果method中僅僅是return await Task.Run(...);這種,可以直接return task本身,這樣就不需要async了,當然call這個method的method還是可能需要async的。

關於async為什麼是必要的,我找到了一下一篇文章

Asynchrony in C# 5 Part Six: Whither async?

一言蔽之,就是跟Ivony說的一樣,避免兼容性問題,而且打個async有提示也不會很費事。

Requiring 「async」 means that we can eliminate all backwards compatibility problems at once; any method that contains an await expression must be 「new construction」 code, not 「old work」 code, because 「old work」 code never had an async modifier.


題主在概念上確實混淆的不行,但是確實async/await這個東西繼承了我軟從VB6開始的光榮傳統(易學難精)。工作中確實沒有碰到幾個真正搞的明白的同事(輪子哥,I大這種論外)。

首先,題主需要搞明白一個概念,就是async不是函數聲明的一部分。從調用者的角度來看,不存在async這個東西。

async是一個專門給編譯器的提示,意思是該函數的實現可能會出現await。至於為啥要有這個提示,而不是編譯器發現函數實現里有await的時候就自動加上async標誌,這是定義語言標準時的選擇,C#(這個feature)的作者也許認為這樣寫讓作者更明確的意識到自己在實現一個包含非同步調用的函數。(我瞎猜的)

下面舉兩個例子

Task& DelayAndCalculate1(int a, int b)
{
return Task.Delay(1000).ContinueWith(t =&> a + b);
}

async Task& DelayAndCalculate2(int a, int b)
{
await Task.Delay(1000);
return a + b;
}

這兩個函數(不算函數名的不同),在函數聲明上是完全沒有區別的。只是其中一個在實現中使用了await,所以C#語法要求我們必須在標示async。

從調用者的角度來看,這兩個函數完全一致(而且行為也一致),都可以使用await關鍵詞unwrap Task類型的返回值(其實這裡有一個叫做GetAwaiter的約定,這裡不展開說)。

另一個佐證就是interface的定義中不能寫async,因為如上所述,async不是函數聲明,而其實編譯函數實現的提示。

最後回到題主的問題為啥有了async還要寫await,其實真正重要的是await(和其他非同步的實現,如例子中的DelayAndCalculate1),有沒有async反而確實不重要。

那麼可不可以如題主所說的設計一個語法糖,不需要寫Task&而寫async int呢?答案是當然可以這麼設計,但這就只是單純的讓編譯器自動把async int翻譯成Task&而已,C#的作者單純沒有這麼設計而已(因為這會使得函數的返回值和聲明時不同,我個人也覺得這會導致更大的confusion)。因為一個返回Task&的函數,不只可以用來await,還有很多別的玩法,語言應該給予開發人員解開和不解開的自由。最簡單的例子:

async Task& ComplexWorkFlow()
{
Task& task1 = DoTask1();
Task& task2 = DoTask2();
Task& task3 = DoTask3UseResultOfTask1(await task1);
Task& task4 = DoTask4UseResultOfTask2(await task2);
return await DoTask5(await task3, await task4);
}

我用短短几行實現了一個相對複雜的工作流,task直接的dependency很明確的表達在代碼里,並且task1和task2可以並行執行,task3和task4可以並行執行(事實上更好的寫法可以讓task1-&>task3完全並行與task2-&>task4)。核心思路就是只有當某個task的執行結果需要被使用的時候才解開這個task的值(等它執行完畢)。

想像一下這個例子裡面如果按照你的語法糖來實現,會有多奇怪。


因為所有await,你都可以看成是

var a = xxxAsync();
await a;

不過通常我們都省略了這個中間變數,但是我告訴你,它們是可以拆分使用的。

考慮如下代碼:

var time0 = DateTime.Now;
var t = Task.Delay(5000);
await Task.Delay(1000);
await Task.Delay(2000);
await t;
Debug.WriteLine(DateTime.Now - time0);

這裡調用了3個Delay,但是總等待時間只花了5秒,因為拆分使用後任務做了統籌。

另外,await 關鍵字使代碼可讀性高很多,非同步和同步的表現有太多不同了。顯式表示出來是有必要的。我們讀代碼的時間比寫代碼的時間長多了,代碼清晰遠比省幾個關鍵字重要,不要當數字達人。


因為這個函數你是想要在中途出現非同步的,就是在await的地方創建新線程繼續執行往後的代碼,舊線程返回回去干該乾的事去

結果你這函數里沒有await,多麼尷尬的一函數

你要知道多線程是幹啥的。

把可以非同步的耗時長的部分使用第二個線程去運行,減少對主線程的影響,保持應用響應、交互


我覺得題主你沒搞清楚多線程和非同步的概念。

對應不同的需求,有很多種多線程執行方法的。async/await只是其中的一種,這種方法叫做非同步調用。舉個栗子吧,你的老闆讓你搬走一千箱貨,你叫來一千個民工每人搬1箱,不管他搬沒搬到指定地點,這個是Task.Run()。你讓人搬到指定地點領根簽子回來找你領錢,這個就是async/await。為啥要await?因為你要等著收回簽子發錢啊,把應發的錢發完這事才算完呢。如果你只需一聲令下:搬走!然後就可以拍拍屁股走人的話,就不要和人說什麼要領簽子回來換錢的話嘛。


沒有await,你的代碼還是同步代碼(當然有了await也不一定非同步),那白寫個async還有什麼意義呢

async double Factorial,編譯時編譯成2個方法,一個是正常的double Factorial,另一個是供非同步調用的async_Factorial,當不使用await調用時選擇調用Factorial,await調用時調用async_Factorial,而這個async_Factorial裡面乾的事情就是將Factorial塞進Task里然後返回await task。

你想要編譯器怎麼編譯成async_Factorial?你以為你說一句編譯成非同步,編譯器就能幹了?


題主搞混概念了

async並不是表明這個方法是非同步方法,而是標明這個方法里有非同步調用

而申明非同步方法一般的做法是返回類型Task


標記為async的方法,如方法體內存在await時候,自動產生一個匿名狀態機類(IAsyncStateMachine),在await前的代碼一個部分編譯成一個方法,await後的代碼一個部分編譯成一個方法,await後的代碼狀態轉換後執行(就可能是非同步的了)。多個await,自己腦補多個狀態,循環、分支怎麼處理自行腦補。

方法返回一個Task對象,並沒有開始執行,這個Task中包含了訪問匿名狀態機類的自動生成的代碼(通過創建AsyncTaskMethodBuilder)。

如async標記的方法中沒有await,那麼不會產生任何匿名類,就是簡單的返回了一個Task。


可以理解為 await下面的代碼相當於回掉函數,只有await結束後,回掉函數才會執行。

async只是表明這個方法中有非同步調用,並不代表這個方法本身是非同步方法


我只寫了一個方法,編譯出來有兩個方法,這不是坑用戶么。

萬一我只想提供非同步方法呢?

而且非同步和同步方法本身內部就會有實現上的不同,同步方法跑在一個線程里可以用 ThreadStatic,非同步方法可能會被調度在其他線程,得用 AsyncLocal。根本不可能實現一個方法編譯器自動產生兩個版本。


我想題主的意思應該是要多一個語法糖來幫助自動包裹一層Task.Run吧。。。async/await有其他用處呢,不止是這個啊。


書名《C#本質論 第四版》18.5.2


await代表的是調用方式,用同步的方式調用非同步方法,好像很繞。。。


加上await其實是「同步執行」,要等待task執行結束,才會執行await 的這個task下面的語句。

然後題主寫的例子,非同步不非同步沒啥區別!


推薦閱讀:

感覺被C#慣壞了,想實戰運用C++該怎麼入手?
就Unity 3D的C# 而言,有什麼是你code review時一看到就是問題的地方?
unity的UnityEngine.dll中extern非static函數是什麼原理??
C#的這個語法是在哪一個版本出現的?
為什麼 C# 應用這麼少?

TAG:C# |