![](/img/trans.png)
[英]Does await Task.CompletedTask mean the async method will run synchronously?
[英]Why does an “async void” method run synchronously?
埃里克·利珀特(Eric Lippert) 在這里說:
無法等待void返回的異步方法; 這是一種“忘了忘了”的方法。 它確實異步工作 ...
它確實異步工作對嗎?
為了測試這一點,我制作了一個Windows窗體應用程序並處理了一個任意事件。 在處理程序內部,我開始進行繁重的計算。 顯然,它阻止了UI的響應:
this.KeyPress += Form1_KeyPressed;
....
private async void Form1_KeyPressed(object sender, EventArgs e)
{
for(int i=0; i<int.max; i++)
;
}
我在埃里克的答案中遺漏了什么?
我在埃里克的答案中遺漏了什么?
我的意思是,它的工作方式與任何其他異步方法一樣。 當您在異步方法中等待某些內容時,該方法的其余部分將被標記為等待的內容的繼續 。 無論異步方法是否無效,都是如此。
在您的示例中,代碼的工作原理與返回任務的異步方法完全相同。 嘗試更改方法以返回任務,您將看到它的行為完全相同。
記住,“異步”並不意味着“我在另一個線程上同時運行”。 這意味着“該方法可能在其操作完成之前返回”。 在操作完成之前可能返回的點標記為“等待”。 您尚未將任何內容標記為“等待”。
我懷疑您相信異步需要並發的神話。 再次: 異步只是意味着方法可以在其工作完成之前返回 。 您開始煮一些雞蛋,門鈴響起,然后將包裝從門廊上取下來,煮完雞蛋,然后打開包裝。 “煮雞蛋”和“獲取郵件”作業不是同時進行的 ,您永遠不會同時進行 。 它們是異步的 。
async
關鍵字所做的只是允許您等待方法中的異步操作(並將結果包裝在任務中)。
每個異步方法都將同步運行,直到到達第一個等待狀態。 如果您不等待任何東西,則此方法(無論它是否返回任務)將同步運行。
如果您的方法是同步的,則通常根本不需要使用異步等待。 但是,如果您希望將CPU密集型操作卸載到其他線程上,以便長時間不阻止UI線程,則可以使用Task.Run
:
await Task.Run(() => CPUIntensiveMethod());
僅分配帶有async
方法不會使其異步調用。對於異步調用,需要在其主體中使用await
關鍵字進行方法調用。
關於此:
無法等待void返回的異步方法; 這是一種“忘了忘了”的方法。 它確實異步工作...
這意味着您不能在另一個這樣的方法中等待此方法。
await Form1_KeyPressed(this, EventArgs.Empty)
為了使代碼正常工作,您需要一個帶有await
關鍵字的方法,例如:
private async void Form1_KeyPressed(object sender, EventArgs e)
{
for(int i=0; i<int.max; i++)
;
// In the body some code like this
await YourMethod();
}
更新后的版本
“異步”關鍵字
將“ async”關鍵字應用於方法時會做什么?
當使用“ async”關鍵字標記方法時,實際上是在告訴編譯器兩件事:
在方法上使用“ async”關鍵字是否會強制該方法的所有調用都是異步的?
否。當您調用標記為“異步”的方法時,該方法開始在curren線程上同步運行。 因此,如果您有一個返回void的同步方法,並且您所做的所有更改都將其標記為“異步”,則該方法的調用仍將同步運行。 無論您將返回類型保留為“ void”還是將其更改為“ Task”,都是如此。 同樣,如果您有一個同步方法返回一些TResult,而您所做的只是將其標記為“異步”並將返回類型更改為“任務”,則該方法的調用仍將同步運行。
將方法標記為“異步”不會影響該方法是同步運行還是異步運行完成。 而是,它使方法可以拆分為多個部分,其中一些部分可以異步運行,以便該方法可以異步完成。 只有在使用“ await”關鍵字顯式編碼一個代碼的地方,這些代碼的邊界才可能出現,因此,如果在方法的代碼中根本不使用“ await”,那么將只有一個代碼,並且由於該代碼將開始運行同步,它(及其整個方法)將同步完成。
有關更多信息,請參見此處:
http://blogs.msdn.com/b/pfxteam/archive/2012/04/12/async-await-faq.aspx
異步/等待允許您創建可以異步運行的代碼。 它甚至可以並行運行(與異步正交)。 但是,它是否並行運行取決於任務的計划方式。
當從UI或ASP.NET上下文(更具體地說,是從具有這些框架的UI控件的上下文中的主線程,從主線程中調用代碼,因為大多數控件只能在擁有它們的線程上訪問)時,它不會被調度默認為后台線程。 您仍然會看到代碼的執行將等待Task完成,然后再繼續,但是由於Task被安排在相同的(主)線程上,因此它將阻止該線程上的任何其他操作(例如,處理UI事件)。
如果知道您的代碼可以安全地在后台線程中執行(再次,這通常是因為您知道您沒有訪問任何線程仿射的控件屬性),則可以使用ConfigureAwait覆蓋調度行為:
await DoWorkAsync().ConfigureAwait(false);
另請參見: 異步/等待-異步編程最佳實踐
異步只是您可以使用異步方法的聲明。 如果您要創建一個任務,例如
private async Task<int> callMe()
{
int i;
for(i = 0; i<int.max; i++)
;
return i;
}
您可以使用await callMe()行來運行代碼; 在您的KeyPressed事件中。 當然,您不必返回任何值,但這只是一個實際示例。
僅僅因為您聲明一個async
方法並不會使其在另一個線程中自動運行。
您必須從Task.Run()
。
因此,在您的情況下,這將是:
private async void Fomr1_KeyPressed(object sender, EventArgs e)
{
await Task.Run(() => {
for(int i=0; i<int.max; i++);
});
}
一些async/await
好起點:
使用Async和Await進行異步編程
.NET並行編程
任務計划程序
我發現Filip Ekberg的解釋很有啟發性:
現在,重要的是要記住,
await
關鍵字之后的所有內容都將連續執行。 使用await
和ContinueWith
之間的區別在於,實際上延續是在調用者線程上進行的,在這種情況下,調用者線程是UI線程。
所以:
private async void Form1_KeyPressed(object sender, EventArgs e)
{
// ----------------------> Surely, on UI thread
await Something(); ------> (May be) on new thread
// ----------------------> Surely, on UI thread
for(int i=0; i<int.max; i++)
;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.