20

考虑以下(基于默认的 MVC 模板),它是在后台发生的一些“东西”的简化版本 - 它完成得很好,并显示了预期的结果,20:

public ActionResult Index()
{
    var task = SlowDouble(10);
    string result;
    if (task.Wait(2000))
    {
        result = task.Result.ToString();
    }
    else
    {
        result = "timeout";
    }

    ViewBag.Message = result;
    return View();
}
internal static Task<long> SlowDouble(long val)
{
    TaskCompletionSource<long> result = new TaskCompletionSource<long>();
    ThreadPool.QueueUserWorkItem(delegate
    {
        Thread.Sleep(50);
        result.SetResult(val * 2);
    });
    return result.Task;
}

但是,现在如果我们async在混合中添加一些:

public static async Task<long> IndirectSlowDouble(long val)
{
    long result = await SlowDouble(val);

    return result;
}

并将路线中的第一行更改为:

var task = IndirectSlowDouble(10);

然后它不起作用;它会超时。如果我们添加断点,方法中的return result;inasync只会在路由已经完成之后发生- 基本上,系统似乎不愿意使用任何线程来恢复async操作,直到请求完成之后。更糟糕的是:如果我们使用过.Wait()(或访问过.Result),那么它将完全死锁。

那么:那是怎么回事?明显的解决方法是“不涉及”,但这在使用库等时并不容易。最终,和之间async没有功能差异(尽管存在明显的结构差异)。SlowDoubleIndirectSlowDouble

注意:控制台/winform/等中完全相同的东西可以正常工作。

4

3 回答 3

11

这与在 ASP.NET(Pre .NET 4.5)中实现同步上下文的方式有关。关于这种行为有很多问题:

Task.WaitAll 在 ASP.NET 中挂起多个等待任务

Asp.net SynchronizationContext 为异步延续锁定 HttpApplication?

在 ASP.NET 4.5 中,本文介绍了同步上下文的新实现。

http://blogs.msdn.com/b/webdev/archive/2012/11/19/all-about-httpruntime-targetframework.aspx

于 2012-11-29T09:15:05.900 回答
8

当您使用.Result时,总是有可能出现死锁,因为.Result本质上是阻塞的。避免死锁的方法是不要阻塞任务(你应该使用asyncawait一直向下)。此处详细描述了该主题:

一种解决方法是添加ConfigureAwait

public static async Task<long> IndirectSlowDouble(long val)
{
    long result = await SlowDouble(val).ConfigureAwait(false);

    return result;
}
于 2012-11-29T09:05:58.367 回答
6

另一个解决方法是在整个过程中使用async/ await

public async Task<ActionResult> Index()
{
    var task = IndirectSlowDouble(10);
    long result = await task;
    ViewBag.Message = result.ToString();
    return View();
}
于 2012-11-29T09:18:00.910 回答