[英]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):
頂級方法調用
GetJsonAsync
(在 UI/ASP.NET 上下文中)。
GetJsonAsync
通過調用HttpClient.GetStringAsync
(仍在上下文中)啟動 REST 請求。
GetStringAsync
返回未完成的Task
,表示 REST 請求未完成。
GetJsonAsync
等待GetStringAsync
返回的Task
。 上下文被捕獲並將用於稍后繼續運行GetJsonAsync
方法。GetJsonAsync
返回未完成的Task
,表示GetJsonAsync
方法未完成。頂級方法在
GetJsonAsync
返回的Task
上同步阻塞。 這會阻塞上下文線程。...最終,REST 請求將完成。 這完成了由
GetStringAsync
返回的Task
。
GetJsonAsync
的延續現在已准備好運行,它等待上下文可用,以便可以在上下文中執行。死鎖。 頂級方法正在阻塞上下文線程,等待
GetJsonAsync
完成,而GetJsonAsync
正在等待上下文空閑以便它可以完成。 對於 UI 示例,“上下文”是 UI 上下文; 對於 ASP.NET 示例,“上下文”是 ASP.NET 請求上下文。 這種類型的死鎖可能是由任一“上下文”引起的。
您應該閱讀的另一個鏈接: 等待、UI 和死鎖! 天啊!
GetDataAsync().Result;
將在GetDataAsync()
返回的任務完成時運行,同時它會阻塞 UI 線程return result.ToString()
)排隊到 UI 線程執行GetDataAsync()
返回的任務將在其排隊的延續運行時完成可以通過提供的替代方案來打破僵局,以避免事實 1 或事實 2。
var data = await GetDataAsync()
代替阻塞 UI 線程,它允許 UI 線程繼續運行var data = Task.Run(GetDataAsync).Result
,這會將延續發布到線程池線程的同步上下文。 這允許GetDataAsync()
返回的任務完成。 這在Stephen Toub 的一篇文章中得到了很好的解釋,大約在他使用DelayAsync()
示例的一半的地方。
我只是在 ASP.NET MVC 項目中再次擺弄這個問題。 當您想從PartialView
調用async
方法時,不允許將PartialView
設為async
。 如果你這樣做,你會得到一個例外。
在要從同步方法調用async
方法的場景中,您可以使用以下簡單的解決方法:
SynchronizationContext
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.