Search code examples
c#asp.net-mvcasync-awaitasynccontroller

ASP.NET MVC - Why are these async tasks running immediately?


I have an ASP.NET MVC async action method which looks like this:

public async Task<ActionResult> IndexAsync()
{
    var tasks = new List<Task<string>>
    {
        GetSomethingAsync("a"),
        GetSomethingAsync("b")
    };

    await Task.WhenAll(tasks);

    return View();
}

private async Task<string> GetSomethingAsync()
{
    var data = await _someService.GetSomethingAsync().ConfigureAwait(false);
    return data.SomeData;
}

Now, when i debug and step over the tasks variable creation, the tasks are executed immediately. In other words, when i hover over tasks in the await line, they say "RanToCompletion".

Why?

From my understanding, the tasks should be created, but be in the "WaitingForActivation" state until triggered by the await Task.WhenAll(tasks) blocking call.

Can someone explain to me what's going on? I've written code like this before and it normally works as expected, so i'm wondering if this is an ASP.NET or ASP.NET MVC async controller thing?

TIA.

EDIT If i change the code to:

var tasks = new List<Task<string>>
{
   Task.Run(() => GetSomethingAsync("a")),
   Task.Run(() => GetSomethingAsync("b"))
};

The method runs as expected (tasks not executed until await).

I've generally never needed to do this before when running async tasks, is this needed in ASP.NET MVC ?


Solution

  • Per your comment you actually did not have any real asynchronous code - so indeed task will return synchronously and be in completed state.

    The easiest way to make method true async is await Task.Yield(). This is fine for unit testing or methods that have to be async for some reason but don't consume much time. If you need to run slow (blocking or just CPU-intensive) methods - Task.Run as you have in the question is a reasonable way to make task running on separate thread.

    Notes

    • marking method async does not by itself make it asynchronous nor await creating any threads by itself.
    • it is better to use real async methods for network calls. ASP.Net has limited thread pull and consuming threads for blocking calls will exhaust the pull under load leading to deadlocks as await'ing method will not be able to find thread to run on.
    • using ConfigureAwait(false) does not prevent load-based deadlocks in ASP.Net and conveniently looses HttpContext.Current and thread's CultureInfo - be careful to use it in ASP.Net especially that there is basically no upside to do so (cost of restoring context on new thread is very low compared to WPF/WinForm cross-thread invocation).