为什么这个任务会导致死锁?
Why does this task cause a deadlock?
.NET 4.7.2 网站
using System;
using System.Threading.Tasks;
using System.Web;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
string input = Input.Text;
bool iWantDeadlock = input == "yes";
string key = iWantDeadlock
? GetHashFragmentAsync(input).GetResultSafely()
: Task.Run(() => GetHashFragmentAsync(input)).Result;
Response.Redirect(key);
}
}
private static async Task<string> GetHashFragmentAsync(string key)
{
await Task.Delay(100);
return "#" + HttpUtility.UrlEncode(key);
}
}
public static class TaskExtensions
{
public static T GetResultSafely<T>(this Task<T> task)
{
return Task.Run(() => task).Result;
}
}
我做了一个非常简单的页面,有一个文本框,提交后将输入放在页面的 hashfragment 中。
我正在阅读有关任务和死锁的内容(在我 运行 阅读其他人的一些代码后导致了死锁)。
解决方案似乎是这样做的:
Task.Run(() => GetHashFragmentAsync(input)).Result;
所以我想,为了清晰和易于使用,让我们将其作为一个扩展。
public static class TaskExtensions
{
public static T GetResultSafely<T>(this Task<T> task)
{
return Task.Run(() => task).Result;
}
}
然而,这会导致死锁。代码是相同的,但工作方式却大不相同。
有人可以解释这种行为吗?
因为当您使用 GetHashFragmentAsync(input).GetResultSafely()
时,您实际上有 2 个任务,而不是 1 个。
GetHashFragmentAsync(input)
returns 已经开始任务。然后你调用 GetResultSafely()
创建另一个任务。
当您在 UI 线程上等待任务时,您会死锁,因为任务无法返回到同步上下文线程。当你有两个任务时,第二个任务可以同步等待,因为父任务不在 UI 线程上,而是在没有同步上下文的 ThreadPool 线程上。
当你调用任何基于 IO 的代码时,你不应该调用 Task.Run
。简单写
protected async void EventHandlerMethod(object sender, EventArgs e)
{
await GetHashFragmentAsync(input);
}
事情是这样的
GetHashFragmentAsync(input) // Start Task number 1
.GetResultSafely() // Start task number 2 from task number 1 (no synchronization context)
// .Result returns back to task 1
第二种情况
Task.Run(() => GetHashFragmentAsync(input)) // UI thread so, capture synchronization context
.Result // Wait for UI thread to finish (deadlock)
当您调用 GetHashFragmentAsync(input)
时,C# async/await 机制会捕获当前同步上下文。方法 returns 一个依赖于 UI 线程的启动任务。您尝试使用 Task.Run
移动关键 UI 线程的任务,但为时已晚。
GetHashFragmentAsync(input)
必须已经在非 UI 线程上调用。将其包裹在 Task.Run
.
中
这是一个通过工厂工作的辅助方法:
public static T GetResultSafely<T>(Func<Task<T>> task)
{
return Task.Run(() => task()).Result;
}
在线程池上调用工厂。
我应该说,通常最好的解决方案是一直使用异步 API,或者保持完全同步。出于正确性和性能原因,混合是有问题的。但它绝对可以安全地完成。
.NET 4.7.2 网站
using System;
using System.Threading.Tasks;
using System.Web;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
string input = Input.Text;
bool iWantDeadlock = input == "yes";
string key = iWantDeadlock
? GetHashFragmentAsync(input).GetResultSafely()
: Task.Run(() => GetHashFragmentAsync(input)).Result;
Response.Redirect(key);
}
}
private static async Task<string> GetHashFragmentAsync(string key)
{
await Task.Delay(100);
return "#" + HttpUtility.UrlEncode(key);
}
}
public static class TaskExtensions
{
public static T GetResultSafely<T>(this Task<T> task)
{
return Task.Run(() => task).Result;
}
}
我做了一个非常简单的页面,有一个文本框,提交后将输入放在页面的 hashfragment 中。
我正在阅读有关任务和死锁的内容(在我 运行 阅读其他人的一些代码后导致了死锁)。
解决方案似乎是这样做的:
Task.Run(() => GetHashFragmentAsync(input)).Result;
所以我想,为了清晰和易于使用,让我们将其作为一个扩展。
public static class TaskExtensions
{
public static T GetResultSafely<T>(this Task<T> task)
{
return Task.Run(() => task).Result;
}
}
然而,这会导致死锁。代码是相同的,但工作方式却大不相同。 有人可以解释这种行为吗?
因为当您使用 GetHashFragmentAsync(input).GetResultSafely()
时,您实际上有 2 个任务,而不是 1 个。
GetHashFragmentAsync(input)
returns 已经开始任务。然后你调用 GetResultSafely()
创建另一个任务。
当您在 UI 线程上等待任务时,您会死锁,因为任务无法返回到同步上下文线程。当你有两个任务时,第二个任务可以同步等待,因为父任务不在 UI 线程上,而是在没有同步上下文的 ThreadPool 线程上。
当你调用任何基于 IO 的代码时,你不应该调用 Task.Run
。简单写
protected async void EventHandlerMethod(object sender, EventArgs e)
{
await GetHashFragmentAsync(input);
}
事情是这样的
GetHashFragmentAsync(input) // Start Task number 1
.GetResultSafely() // Start task number 2 from task number 1 (no synchronization context)
// .Result returns back to task 1
第二种情况
Task.Run(() => GetHashFragmentAsync(input)) // UI thread so, capture synchronization context
.Result // Wait for UI thread to finish (deadlock)
当您调用 GetHashFragmentAsync(input)
时,C# async/await 机制会捕获当前同步上下文。方法 returns 一个依赖于 UI 线程的启动任务。您尝试使用 Task.Run
移动关键 UI 线程的任务,但为时已晚。
GetHashFragmentAsync(input)
必须已经在非 UI 线程上调用。将其包裹在 Task.Run
.
这是一个通过工厂工作的辅助方法:
public static T GetResultSafely<T>(Func<Task<T>> task)
{
return Task.Run(() => task()).Result;
}
在线程池上调用工厂。
我应该说,通常最好的解决方案是一直使用异步 API,或者保持完全同步。出于正确性和性能原因,混合是有问题的。但它绝对可以安全地完成。