2015-11-05 143 views
1

我正在編寫一個小的控制檯應用程序,以嘗試熟悉使用async/await。在這個應用程序中,我意外地創建了一個無限遞歸循環(我現在已經修復)。這個無限遞歸循環的行爲讓我感到驚訝。它並沒有拋出StackOverflowException,而是陷入僵局。無限遞歸調用異步/等待永不拋出異常

考慮下面的例子。如果Foo()被調用runAsync設置爲false,則拋出StackOverflowException。但是當runAsynctrue時,它變得死鎖(或至少看起來像)。任何人都可以解釋爲什麼行爲如此不同?

bool runAsync; 
void Foo() 
{ 
    Task.WaitAll(Bar(),Bar()); 
} 

async Task Bar() 
{ 
    if (runAsync) 
     await Task.Run(Foo).ConfigureAwait(false); 
    else 
     Foo(); 
} 

回答

4

它並沒有真正死鎖。這很快耗盡了線程池中的可用線程。然後,每500毫秒注入一個新線程。你可以觀察到,當你在那裏登錄一些Console.WriteLine

基本上,這段代碼是無效的,因爲它壓倒了線程池。這種精神沒有什麼可以投入生產。

如果你使所有等待的異步而不是使用Task.WaitAll你轉而明顯的死鎖變成失控的內存泄漏。這對你來說可能是一個有趣的實驗。

1

異步版本不會死鎖(正如usr解釋的),但它不會拋出StackOverflowException,因爲它不依賴於堆棧。

堆棧是爲線程保留的內存區域(與所有線程共享的堆不同)。

當您調用異步方法時,它會同步運行(即使用相同的線程和堆棧),直到它達到未完成任務的等待狀態。此時,該方法的其餘部分被安排爲延續並且線程被釋放(連同其堆棧)。

所以,當你使用Task.Run你卸載Foo到另一個線程ThreadPool用乾淨的堆棧,所以你永遠不會得到StackOverflowException

但是,您可能會達到OutOfMemoryException,因爲異步方法的狀態機存儲在堆中,可用於恢復所有線程。此示例將非常快速地拋出,因爲您不會耗盡ThreadPool

static void Main() 
{ 
    Foo().Wait(); 
} 

static async Task Foo() 
{ 
    await Task.Yield(); 
    await Foo(); 
}