2017-06-29 28 views
4

我創建了一個簡單的WebAPI項目,一個控制器和一個方法:在ASP.NET的上下文中,爲什麼在調用異步方法時沒有Task.Run(...)。結果死鎖?

public static class DoIt 
{ 
    public static async Task<string> GetStrAsync(Uri uri) 
    { 
     using (var client = new HttpClient()) 
     { 
      var str = await client.GetStringAsync(uri); 
      return str; 
     } 
    } 
} 

public class TaskRunResultController : ApiController 
{ 
    public string Get() 
    { 
     var task = Task.Run(() => 
      DoIt.GetStrAsync(new Uri("http://google.com")) 
     ); 
     var result = task.Result; 

     return result; 
    } 
} 

我有異步有很好的理解/等待和任務;幾乎虔誠地跟隨斯蒂芬克利裏。只是.Result的存在讓我着急,我預計這會陷入僵局。據我所知,Task.Run(...)是浪費,導致線程被佔用,等待異步DoIt()完成。

問題是這不是死鎖,它給我心悸。

我看到一些答案,如https://stackoverflow.com/a/32607091/1801382,我也觀察到SynchronizationContext.Current在執行lambda時爲空。但是,也有類似的問題要問我爲什麼上面的代碼確實是死鎖,並且我發現在使用ConfigureAwait(false)(不捕獲上下文)和.Result的情況下會發生死鎖。

什麼給?

回答

5

Result本身不會導致死鎖。當代碼的兩部分都相互等待時,會出現死鎖。一個Result只是一個等待,所以它可能是一個死鎖的一部分,但它不一定總是導致死鎖。

standard example,該Result等待完成而持有到上下文任務,但任務無法完成,因爲它是等待背景可以免費。所以有一個僵局 - 他們在等待對方。

ASP.NET Core does not have a context at all,所以Result不會在那裏死鎖。 (由於其他原因,這仍然不是一個好主意,但它不會死鎖)。

問題是這不是死鎖,它給了我心悸。

這是因爲Task.Run任務不需要上下文來完成。所以調用Task.Run(...).Result是安全的。 Result正在等待任務完成,並且它保留上下文(阻止請求的任何其他部分在該上下文中執行),但這沒關係,因爲Task.Run任務根本不需要上下文。

Task.Run對於ASP.NET(出於其他原因)而言仍然不是一個好主意,但是這是一個非常有用的技術,有時會很有用。它不會發生死鎖,因爲Task.Run任務不需要上下文。

不過,也有礦類似的問題問爲什麼上面的代碼不會死鎖,

類似,但並不確切。仔細查看這些問題中的每條代碼語句,並問問自己它運行的是什麼上下文。

我發現在使用ConfigureAwait(false)(不捕獲上下文)和.Result的情況下發生死鎖。

Result /上下文死鎖是一個非常常見的 - 可能是最常見的一個。但它不是那裏唯一的死鎖場景。

一個更加困難的僵局,可以顯示出來,如果你內async代碼塊沒有上下文)的Here's an example。不過,這種情況更爲罕見。

+0

謝謝,斯蒂芬。我懷疑答案基本上是「這是因爲Task.Run任務不需要上下文來完成」,但是猶豫稱這個「安全」。我瞭解其他影響。感謝封鎖的例子;因爲現在是2017年,更罕見的情況仍然有效嗎?您還聲明這不會發生在GUI或ASP.NET代碼中 - 在哪種情況下_does_它會死鎖? – aholmes

+0

是的。 'await'仍然使用'ExecuteSynchronously',所以更稀有的場景仍然有效。在這種情況下,這是一個死鎖,兩個線程池線程正在等待對方。 –

0

我就給你簡單的回答倒過來:如何產生死鎖:

允許在正在執行同步和卸載一些異步調用到一個單獨的任務Click處理程序啓動,然後等待結果

private void MenuItem_Click(object sender, RoutedEventArgs e) 
{ 
    var t = Task.Run(() => DeadlockProducer(sender as MenuItem)); 
    var result = t.Result; 
} 

private async Task<int> DeadlockProducer(MenuItem sender) 
{ 
    await Task.Delay(1); 
    Dispatcher.Invoke(() => sender.Header = "Life sucks"); 
    return 0; 
} 

的異步方法是做其他壞事:它試圖同步帶來一些UI改變,但是在UI線程仍在等待任務結果...

所以基本上,Task.Run是一半的出路,並且可以用於許多格式良好的異步代碼,但是有辦法打破它,所以它不是一個可靠的解決方案。

此示例代碼是在WPF的上下文中編寫的,但我想ASP.Net可能會被濫用以產生類似的行爲。

相關問題