简体   繁体   English

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

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

I created a simple WebApi project with a single controller and a single 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;
    }
}

I have a good understanding of async/await and tasks; 我非常了解async / await和tasks; almost religiously following Stephen Cleary. 斯蒂芬克莱里几乎虔诚地跟随他。 Just the existence of .Result makes me anxious, and I expect this to deadlock. 只是存在.Result让我焦虑,我希望这会陷入僵局。 I understand that the Task.Run(...) is wasteful, causing a thread to be occupied while waiting for the async DoIt() to finish. 我知道Task.Run(...)是浪费的,导致在等待异步DoIt()完成时占用一个线程。

The problem is this is not deadlocking , and it's giving me heart palpitations. 问题是这不是死锁 ,而是让我心悸。

I see some answers like https://stackoverflow.com/a/32607091/1801382 , and I've also observed that SynchronizationContext.Current is null when the lambda is executing. 我看到一些答案,如https://stackoverflow.com/a/32607091/1801382 ,我还发现当lambda正在执行时, SynchronizationContext.Current为null。 However, there are similar questions to mine asking why the above code does deadlock, and I've seen deadlocks occur in cases where ConfigureAwait(false) is used (not capturing the context) in conjunction with .Result . 但是,我也有类似的问题,问为什么上面的代码会出现死锁,而且我看到在使用了ConfigureAwait(false) (不捕获上下文)和.Result情况下发生了死锁。

What gives? 是什么赋予了?

Result by itself isn't going to cause a deadlock. Result本身不会导致死锁。 A deadlock is when two parts of the code are both waiting for each other. 死锁是指代码的两个部分都在等待彼此。 A Result is just one wait, so it can be part of a deadlock, but it doesn't necessarily always cause a deadlock. Result只是一次等待,因此它可能是死锁的一部分,但它不一定总是导致死锁。

In the standard example , the Result waits for the task to complete while holding onto the context , but the task can't complete because it's waiting for the context to be free . 标准示例中Result 在保持上下文的同时等待任务完成,但是任务无法完成,因为它正在等待上下文空闲 So there's a deadlock - they're waiting for each other. 所以有一个僵局 - 他们正在等待对方。

ASP.NET Core does not have a context at all , so Result won't deadlock there. ASP.NET Core根本没有上下文 ,因此Result不会死锁。 (It's still not a good idea for other reasons, but it won't deadlock ). (由于其他原因,它仍然不是一个好主意,但它不会陷入僵局 )。

The problem is this is not deadlocking, and it's giving me heart palpitations. 问题是这不是死锁,而是让我心悸。

This is because the Task.Run task does not require the context to complete. 这是因为Task.Run任务不需要完成上下文。 So calling Task.Run(...).Result is safe. 所以调用Task.Run(...).Result是安全的。 The Result is waiting for the task to complete, and it is holding onto the context (preventing any other parts of the request from executing in that context), but that's OK because the Task.Run task doesn't need the context at all. Result正在等待任务完成,并且它保持上下文(阻止请求的任何其他部分在该上下文中执行),但这没关系,因为Task.Run任务根本不需要上下文。

Task.Run is still not a good idea in general on ASP.NET (for other reasons), but this is a perfectly valid technique that is useful from time to time. 在ASP.NET上, Task.Run仍然不是一个好主意(出于其他原因),但这是一种非常有用的技术,不时有用。 It won't ever deadlock, because the Task.Run task doesn't need the context. 它不会死锁,因为Task.Run任务不需要上下文。

However, there are similar questions to mine asking why the above code does deadlock, 但是,我也有类似的问题,问为什么上面的代码会出现死锁,

Similar but not exact. 相似但不完全相同。 Take a careful look at each code statement in those questions and ask yourself what context it runs on. 仔细查看这些问题中的每个代码语句,并问自己它运行的上下文。

and I've seen deadlocks occur in cases where ConfigureAwait(false) is used (not capturing the context) in conjunction with .Result. 我已经看到在与.Result一起使用ConfigureAwait(false)(不捕获上下文)的情况下会发生死锁。

The Result /context deadlock is a very common one - probably the most common one. Result /上下文死锁是一个非常常见的 - 可能是最常见的死锁。 But it's not the only deadlock scenario out there. 但它不是那里唯一的僵局。

Here's an example of a much more difficult deadlock that can show up if you block within async code ( without a context). 这是一个更加困难的死锁示例 ,如果您 async代码中阻塞( 没有上下文),则会出现这种死锁。 This kind of scenario is much more rare, though. 但是,这种情况更为罕见。

I'll give you as short answer the other way around: how to produce a deadlock: 我会以相反的方式给你一个简短的答案:如何产生死锁:

Lets start in a Click handler that is executing synchronous and offloading some async call to a separate Task, then waiting for the result 让我们从一个执行同步的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;
}

The async method is doing another bad thing: it tries to bring some UI change synchronously, but the UI thread is still waiting for the task result... 异步方法正在做另一件坏事:它试图同步带来一些UI更改,但UI线程仍在等待任务结果...

So basically, Task.Run is half way out and will work for a lot of well formed async code, but there are ways to break it, so its not a reliable solution. 所以基本上, Task.Run已经完成了一半,并且可以用于很多格式良好的异步代码,但是有很多方法可以解决它,所以它不是一个可靠的解决方案。

This sample code was written in context of WPF, but I guess ASP.Net could be abused to produce similar behavior. 这个示例代码是在WPF的上下文中编写的,但我想ASP.Net可能会被滥用来产生类似的行为。

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

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