簡體   English   中英

導致死鎖的異步/等待示例

[英]An async/await example that causes a deadlock

我遇到了一些使用 c# 的async / await關鍵字進行異步編程的最佳實踐(我是 c# 5.0 的新手)。

給出的建議之一如下:

穩定性:了解您的同步上下文

...一些同步上下文是不可重入的和單線程的。 這意味着在給定時間只能在上下文中執行一個工作單元。 這方面的一個示例是 Windows UI 線程或 ASP.NET 請求上下文。 在這些單線程同步上下文中,很容易讓自己陷入死鎖。 如果您從單線程上下文中生成任務,然后在上下文中等待該任務,您的等待代碼可能會阻塞后台任務。

public ActionResult ActionAsync()
{
    // DEADLOCK: this blocks on the async task
    var data = GetDataAsync().Result;

    return View(data);
}

private async Task<string> GetDataAsync()
{
    // a very simple async method
    var result = await MyWebService.GetDataAsync();
    return result.ToString();
}

如果我嘗試自己剖析它,主線程會在MyWebService.GetDataAsync(); ,但由於主線程在那里等待,它在GetDataAsync().Result中等待結果。 同時,說數據准備好了。 為什么主線程不繼續它的延續邏輯並從GetDataAsync()返回字符串結果?

有人可以解釋一下為什么上面的例子中會出現死鎖嗎? 我完全不知道問題是什么......

看看這個例子,斯蒂芬給你一個明確的答案:

所以這就是發生的事情,從頂級方法開始( Button1_Click用於 UI / MyController.Get用於 ASP.NET):

  1. 頂級方法調用GetJsonAsync (在 UI/ASP.NET 上下文中)。

  2. GetJsonAsync通過調用HttpClient.GetStringAsync (仍在上下文中)啟動 REST 請求。

  3. GetStringAsync返回未完成的Task ,表示 REST 請求未完成。

  4. GetJsonAsync等待GetStringAsync返回的Task 上下文被捕獲並將用於稍后繼續運行GetJsonAsync方法。 GetJsonAsync返回未完成的Task ,表示GetJsonAsync方法未完成。

  5. 頂級方法在GetJsonAsync返回的Task上同步阻塞。 這會阻塞上下文線程。

  6. ...最終,REST 請求將完成。 這完成了由GetStringAsync返回的Task

  7. GetJsonAsync的延續現在已准備好運行,它等待上下文可用,以便可以在上下文中執行。

  8. 死鎖 頂級方法正在阻塞上下文線程,等待GetJsonAsync完成,而GetJsonAsync正在等待上下文空閑以便它可以完成。 對於 UI 示例,“上下文”是 UI 上下文; 對於 ASP.NET 示例,“上下文”是 ASP.NET 請求上下文。 這種類型的死鎖可能是由任一“上下文”引起的。

您應該閱讀的另一個鏈接: 等待、UI 和死鎖! 天啊!

  • 事實 1: GetDataAsync().Result; 將在GetDataAsync()返回的任務完成時運行,同時它會阻塞 UI 線程
  • 事實 2:等待的延續( return result.ToString() )排隊到 UI 線程執行
  • 事實 3: GetDataAsync()返回的任務將在其排隊的延續運行時完成
  • 事實 4:排隊的延續永遠不會運行,因為 UI 線程被阻塞(事實 1)

僵局!

可以通過提供的替代方案來打破僵局,以避免事實 1 或事實 2。

  • 修復 1:避免 1,4。 使用var data = await GetDataAsync()代替阻塞 UI 線程,它允許 UI 線程繼續運行
  • 修復 2:避免 2,3。 將等待的延續排隊到未阻塞的不同線程,例如使用var data = Task.Run(GetDataAsync).Result ,這會將延續發布到線程池線程的同步上下文。 這允許GetDataAsync()返回的任務完成。

這在Stephen Toub 的一篇文章中得到了很好的解釋,大約在他使用DelayAsync()示例的一半的地方。

我只是在 ASP.NET MVC 項目中再次擺弄這個問題。 當您想從PartialView調用async方法時,不允許將PartialView設為async 如果你這樣做,你會得到一個例外。

在要從同步方法調用async方法的場景中,您可以使用以下簡單的解決方法:

  1. 在調用之前,清除SynchronizationContext
  2. 調用吧,這里不會再出現死鎖了,等它結束
  3. 恢復SynchronizationContext

例子:

public ActionResult DisplayUserInfo(string userName)
{
    // trick to prevent deadlocks of calling async method 
    // and waiting for on a sync UI thread.
    var syncContext = SynchronizationContext.Current;
    SynchronizationContext.SetSynchronizationContext(null);

    //  this is the async call, wait for the result (!)
    var model = _asyncService.GetUserInfo(Username).Result;

    // restore the context
    SynchronizationContext.SetSynchronizationContext(syncContext);

    return PartialView("_UserInfo", model);
}

另一個要點是你不應該阻塞任務,並且一直使用異步來防止死鎖。 那么這將是所有異步而不是同步阻塞。

public async Task<ActionResult> ActionAsync()
{

    var data = await GetDataAsync();

    return View(data);
}

private async Task<string> GetDataAsync()
{
    // a very simple async method
    var result = await MyWebService.GetDataAsync();
    return result.ToString();
}

我想到的一個解決方法是在詢問結果之前對任務使用Join擴展方法。

代碼如下所示:

public ActionResult ActionAsync()
{
  var task = GetDataAsync();
  task.Join();
  var data = task.Result;

  return View(data);
}

join方法在哪里:

public static class TaskExtensions
{
    public static void Join(this Task task)
    {
        var currentDispatcher = Dispatcher.CurrentDispatcher;
        while (!task.IsCompleted)
        {
            // Make the dispatcher allow this thread to work on other things
            currentDispatcher.Invoke(delegate { }, DispatcherPriority.SystemIdle);
        }
    }
}

我對這個領域的了解還不夠,看不到這個解決方案的缺點(如果有的話)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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