为什么此任务会导致死锁?

时间:2018-10-12 12:44:17

标签: c# asynchronous webforms task 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;
    }
}

我制作了一个非常简单的页面,带有一个文本框,提交后将输入内容放入页面的哈希片段中。

我正在阅读“任务和死锁”(在我遇到其他导致一个死锁的代码之后)。

解决方案似乎是这样做的:

Task.Run(() => GetHashFragmentAsync(input)).Result;

所以我想,让我们做一个扩展以使其清晰易用。

public static class TaskExtensions
{
    public static T GetResultSafely<T>(this Task<T> task)
    {
        return Task.Run(() => task).Result;
    }
}

但是,这将导致死锁。代码是相同的,但工作方式却大不相同。 有人可以解释这种行为吗?

2 个答案:

答案 0 :(得分:0)

因为使用GetHashFragmentAsync(input).GetResultSafely()时实际上有2个任务而不是1个。

GetHashFragmentAsync(input)返回已经开始的任务。然后调用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)

答案 1 :(得分:-2)

调用GetHashFragmentAsync(input)时,当前的同步上下文由C#异步/等待机制捕获。该方法返回取决于UI线程的已启动任务。您尝试使用Task.Run移动关键UI线程的任务,但为时已晚。

GetHashFragmentAsync(input)必须已经在非UI线程上被调用。将其包装在Task.Run中。

以下是通过工厂建立工作的辅助方法:

public static T GetResultSafely<T>(Func<Task<T>> task)
{
    return Task.Run(() => task()).Result;
}

在线程池上调用工厂。

我应该说,通常最好的解决方案是使用异步API,或者保持完全同步。出于正确性和性能的原因,混合是有问题的。但这绝对可以安全地完成。