繁体   English   中英

在ASP.NET的上下文中,为什么在调用异步方法时Task.Run(...).Result没有死锁?

[英]In the context of ASP.NET, why doesn't Task.Run(…).Result deadlock when calling an async method?

我用一个控制器和一个方法创建了一个简单的WebApi项目:

public static class DoIt
{
    public static async Task<string> GetStrAsync(Uri uri)
    {
        using (var client = new HttpClient())
        {
            var str = await client.GetStringAsync(uri);
            return str;
        }
    }
}

public class TaskRunResultController : ApiController
{
    public string Get()
    {
        var task = Task.Run(() =>
            DoIt.GetStrAsync(new Uri("http://google.com"))
        );
        var result = task.Result;

        return result;
    }
}

我非常了解async / await和tasks; 斯蒂芬克莱里几乎虔诚地跟随他。 只是存在.Result让我焦虑,我希望这会陷入僵局。 我知道Task.Run(...)是浪费的,导致在等待异步DoIt()完成时占用一个线程。

问题是这不是死锁 ,而是让我心悸。

我看到一些答案,如https://stackoverflow.com/a/32607091/1801382 ,我还发现当lambda正在执行时, SynchronizationContext.Current为null。 但是,我也有类似的问题,问为什么上面的代码会出现死锁,而且我看到在使用了ConfigureAwait(false) (不捕获上下文)和.Result情况下发生了死锁。

是什么赋予了?

Result本身不会导致死锁。 死锁是指代码的两个部分都在等待彼此。 Result只是一次等待,因此它可能是死锁的一部分,但它不一定总是导致死锁。

标准示例中Result 在保持上下文的同时等待任务完成,但是任务无法完成,因为它正在等待上下文空闲 所以有一个僵局 - 他们正在等待对方。

ASP.NET Core根本没有上下文 ,因此Result不会死锁。 (由于其他原因,它仍然不是一个好主意,但它不会陷入僵局 )。

问题是这不是死锁,而是让我心悸。

这是因为Task.Run任务不需要完成上下文。 所以调用Task.Run(...).Result是安全的。 Result正在等待任务完成,并且它保持上下文(阻止请求的任何其他部分在该上下文中执行),但这没关系,因为Task.Run任务根本不需要上下文。

在ASP.NET上, Task.Run仍然不是一个好主意(出于其他原因),但这是一种非常有用的技术,不时有用。 它不会死锁,因为Task.Run任务不需要上下文。

但是,我也有类似的问题,问为什么上面的代码会出现死锁,

相似但不完全相同。 仔细查看这些问题中的每个代码语句,并问自己它运行的上下文。

我已经看到在与.Result一起使用ConfigureAwait(false)(不捕获上下文)的情况下会发生死锁。

Result /上下文死锁是一个非常常见的 - 可能是最常见的死锁。 但它不是那里唯一的僵局。

这是一个更加困难的死锁示例 ,如果您 async代码中阻塞( 没有上下文),则会出现这种死锁。 但是,这种情况更为罕见。

我会以相反的方式给你一个简短的答案:如何产生死锁:

让我们从一个执行同步的Click处理程序开始,并将一些异步调用卸载到一个单独的Task,然后等待结果

private void MenuItem_Click(object sender, RoutedEventArgs e)
{
    var t = Task.Run(() => DeadlockProducer(sender as MenuItem));
    var result = t.Result;
}

private async Task<int> DeadlockProducer(MenuItem sender)
{
    await Task.Delay(1);
    Dispatcher.Invoke(() => sender.Header = "Life sucks");
    return 0;
}

异步方法正在做另一件坏事:它试图同步带来一些UI更改,但UI线程仍在等待任务结果...

所以基本上, Task.Run已经完成了一半,并且可以用于很多格式良好的异步代码,但是有很多方法可以解决它,所以它不是一个可靠的解决方案。

这个示例代码是在WPF的上下文中编写的,但我想ASP.Net可能会被滥用来产生类似的行为。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM