-4

Consider this code on a console application written with vs 2015:

using System;
using System.Threading.Tasks;

public class Example
{
    public static void Main()
    {
        Console.WriteLine("Wait please....");
        DateTime time1 = DateTime.Now;
        Task task1 = MyAsyncFun1();
        Task task2 = MyAsyncFun2();
        task1.Wait();
        task2.Wait();
        DateTime time2 = DateTime.Now;
        Console.WriteLine("time elapsed: " + 
        (time2 -  time1).TotalSeconds);
        Console.ReadLine();
    }
    private static async Task MyAsyncFun1()
    {
        await Task.Yield();
        System.Threading.Thread.Sleep(2000);
    }
    private static async Task MyAsyncFun2()
    {
        await Task.Yield();
        System.Threading.Thread.Sleep(2000);
    }
}

There are two methods called asynchronously and all work fine, the 'time elapsed' does not exceeded 2 seconds becouse the execution is parallel.

But when i wrote this code inside an acion method of an ApiController like this:

public class MyController : ApiController
{
    private static async Task MyAsyncFun1()
    {
        await Task.Yield();
        System.Threading.Thread.Sleep(2000);
    }
    private static async Task MyAsyncFun2()
    {
        await Task.Yield();
        System.Threading.Thread.Sleep(2000);
    }
    public async Task<ApiResult> MyAction([FromBody] Mymodel model)
    {
        DateTime time1 = DateTime.Now;
        Task task1 = MyAsyncFun1();
        Task task2 = MyAsyncFun2();
        task1.Wait();
        task2.Wait();
        DateTime time2 = DateTime.Now;
        double d=(time2 - time1).TotalSeconds;
        return .....;
    }
}

the execution hang indefinitely on the task1.Wait() statement.

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
  • 7
    I don't see what DI has to do with this, and why would you `Wait()` when you can `await` in an async method? – Crowcoder Apr 03 '18 at 11:49
  • 4
    "**Don't block on asynchronous code**" > never use `.Wait()` on an `async Task` method – Camilo Terevinto Apr 03 '18 at 11:50
  • 3
    I see no dependency injection here? – ProgrammingLlama Apr 03 '18 at 11:51
  • I apologise for the DI in the title, thi is a my error, the DI does not matter with this question; the problem is the asynchronous that hang on API controller – user3352251 Apr 03 '18 at 11:57
  • 3
    Console applications don't have synchronization contexts. asp.net does ("old" asp.net anyway. I believe they've eliminated it for asp.net core) – Damien_The_Unbeliever Apr 03 '18 at 12:01
  • You can use `Task.Delay(2000)` instead of `System.Threading.Thread.Sleep(2000)` – Magnus Apr 03 '18 at 12:11
  • 2
    the OP obviously does not want write a program with Thread.Sleep() in it. he probably will replace that with some other method that takes a lot of time to execute, that is not an async method. this is just an example of demonstrating the behaviour that he is asking a question about. to explain why this particular example. we can all see it will probably work if everything is replaced with await, but thus far no one can properly explain why this works in console and not in a web controller. and I think its a damn good question. – Gerrie Pretorius Apr 03 '18 at 12:23
  • 3
    @GerriePretorius - he's deadlocking the synchronization context. There's so many examples of this type of problem I don't see any value in constructing yet another answer that points that out. – Damien_The_Unbeliever Apr 03 '18 at 16:17
  • @GerriePretorius It is a well known rule that you shouldn't block ASP.NET context's as it can lead to deadlock. Unless you are using ASP.NET Core. https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html –  Apr 03 '18 at 23:51

1 Answers1

1

Your code is presumably running in ASP.NET (not ASP.NET Core), and has a SynchronizationContext that requires exclusive access, when you block on async code, you risk deadlocking, like you do here.

What is happening here is, chronologically your first awaited incomplete task happens inside MyAsyncFun1, awaiting Task.Yield(), this returns an incomplete task immediately and will later post the continuation (the sleep) back to the current SynchronizationContext.

Later you call .Wait() on task1, which will block until it completes, however it needs the SynchronizationContext to be available, but your waiting blocking thread is using it, so it's a stalemate, deadlock.

You could use .ConfigureAwait(false) on the tasks you are awaiting to ensure they don't require the continuations to be posted back to the current SynchronizationContext, but even better of course is to just await everywhere, and use async top-to-bottom.

Stuart
  • 5,358
  • 19
  • 28