Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't this deadlock in ASP.NET Core Web API?

I read Stephen Cleary's post Don't Block on Async Code, so I created an ASP.NET Core Web API project:

class AsyncTest
{
    public async Task<string> DoSomethingAsync()
    {
        await Task.Delay(3000);
        return "I'm back";
    }
}

[Route("api/[controller]")]
public class ValuesController : Controller
{
    // GET api/values
    [HttpGet]
    public IEnumerable<string> Get()
    {
        var asyncTest = new AsyncTest();
        var result = asyncTest.DoSomethingAsync().Result;
        return new string[] { result };
    }
}

I expected this piece of code to deadlocked, because once await Task.Delay(3000); completes, DoSomethingAsync() needs to enter the request context which is blocked by var result = asyncTest.DoSomethingAsync().Result;. But it doesn't deadlock and returns without problem! Does ASP.NET Core Web API behave different?

I'm using dotnet --info:

.NET Command Line Tools (2.1.2)

Product Information:
 Version:            2.1.2
 Commit SHA-1 hash:  5695315371

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.14393
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\2.1.2\

Microsoft .NET Core Shared Framework Host

  Version  : 2.0.3
  Build    : a9190d4a75f4a982ae4b4fa8d1a24526566c69df

Also I created a normal .NET Framework 4.6.1 console app:

class Program
{
    static void Main(string[] args)
    {
        var asyncTest = new AsyncTest();

        var result = asyncTest.DoSomethingAsync().Result;
    }
}

class AsyncTest
{
    public async Task<string> DoSomethingAsync()
    {
        await Task.Delay(3000);
        return "I'm back";
    }
}

I thought this should also deadlock, as var result = asyncTest.DoSomethingAsync().Result; blocks the main thread, and DoSomethingAsync() captures the main thread's context on await Task.Delay(3000);. Once await Task.Delay(3000); completes, in order to resume, DoSomethingAsync() needs to enter the main thread's context which is blocked. But it actually doesn't deadlock either. Where am I wrong here?

UPDATE:

As pointed out by others, there isn't Synchronization context in ASP.NET Core & console applications, now my questions is that as in legacy ASP.NET applications, the synchronization context takes care of restoring things like HttpContext.Current on the continuation when the awaited async action finished and the exectution resumes, so how does an ASP.NET Core application make these things like HttpContext.Current available once it resumes on a new picked thread pool thread?

Moreover, for any operation which doesn't have its own delicated thread, let's say it's running on a thread pool thread (like ASP.NET applications, ASP.NET Core applications etc.) and await on another async operation. As it starts awaiting, it yields the thread it's currently running on. But once the awaited async operation completes, it resumes on another thread pool thread, how can the previous thread context (like local variables etc.) can be restored to the new picked one? For example in the below code, after var result = await DoSomethingAsync(); there is a new thread from the thread pool serving the rest code. So how does it restore name, age, then return new string[] { $"{name} at {age}: {result}" }; can use them? Local variables are stored in thread call stack which belongs to a particular thread. So I think there must be thread context switching involved for the continuation. Before the await call, the current thread pool thread's thread context needs to be stored, after the await call, the stored thread context needs to be restored to the new picked thread context. If so, the cost would be not cheap. Is my understanding correct here?

[HttpGet]
public async Task<IEnumerable<string>> GetAsync()
{
    var name = "John Doe";
    var age = 20;
    var result = await DoSomethingAsync();
    return new string[] { $"{name} at {age}: {result}" };
}
like image 910
cateyes Avatar asked Feb 04 '18 08:02

cateyes


2 Answers

Because ASP.NET Core does not have a Synchronization Context.

Update

Jon Skeet has a great blog series if you want to learn how it works under the hood: https://codeblog.jonskeet.uk/category/eduasync/

like image 91
Paulo Morgado Avatar answered Nov 13 '22 20:11

Paulo Morgado


For ASP.NET Core, there is no SynchronizationContext, which means the specified blocking situation cannot occur in ASP.NET Core. The same goes for console application.

like image 3
tia Avatar answered Nov 13 '22 18:11

tia