簡體   English   中英

為什么“異步無效”方法會同步運行?

[英]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”關鍵字標記方法時,實際上是在告訴編譯器兩件事:

  1. 您告訴編譯器您希望能夠在方法內部使用“ await”關鍵字(可以且僅當其所在的方法或lambda標記為異步時才可以使用await關鍵字)。 這樣做是在告訴編譯器使用狀態機編譯該方法,這樣該方法將能夠掛起,然后在等待點異步恢復。
  2. 您要告訴編譯器“提升”方法的結果或返回類型中可能發生的任何異常。 對於返回Task或Task的方法,這意味着該方法中未處理的任何返回值或異常都存儲在結果任務中。 對於返回void的方法,這意味着任何異常都會通過該方法初始調用時當前使用的任何“ SynchronizationContext”傳播到調用者的上下文。

在方法上使用“ 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關鍵字之后的所有內容都將連續執行。 使用awaitContinueWith之間的區別在於,實際上延續是在調用者線程上進行的,在這種情況下,調用者線程是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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM