2

I am trying to understand the async/await mechanism with MVC. Now I do not consider a use case where I would go out of the "normal flow" (using either full sync or full async end-to-end). I just want to make sure to understand the why it doesn't work here.

When "SyncMethod" is called it hangs indefinitely and never returns.

When "AsyncMethod" is called it returns a view quickly without hanging.

When "TaskMethod" is called it returns a view quickly without hanging as well.

I am not exactly sure to understand why when a synchronous method calls an async method it is impossible to return a result.

What am I missing here?

using System;
using System.Threading.Tasks;
using System.Web.Mvc;

namespace MvcAsyncAwaitTest.Controllers
{
    public class HomeController : Controller
    {
        /// <summary>
        /// Synchronous method running async method as sync
        /// Hangs at Hello().Result, never returns
        /// </summary>
        /// <returns></returns>
        public ActionResult SyncMethod()
        {
            ViewBag.Message = Hello().Result;
            return View();
        }

        /// <summary>
        /// Asynchronous method awaiting asynchronous method
        /// Do not hang 
        /// </summary>
        /// <returns></returns>
        public async Task<ActionResult> AsyncMethod()
        {
            ViewBag.Message = await Hello();
            return View("Index");
        }

        /// <summary>
        /// Synchronous method running a task based method synchronously
        /// Returns a valid result
        /// </summary>
        /// <returns></returns>
        public ActionResult TaskMethod()
        {
            ViewBag.Message = Hello2().Result;

            return View("index");
        }

        private async Task<string> Hello()
        {
            return await HelloImpl();
        }

        private Task<string> Hello2()
        {
            return Task.Run(() => "Hello world 2");
        }

        private async Task<String> HelloImpl()
        {
            return await Task.Run(() => "Hello World");
        }
    }
}
Erick
  • 5,969
  • 10
  • 42
  • 61
  • 1
    Stephen Cleary explains the async/deadlock dilemma here: http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html – dcastro Dec 16 '13 at 21:56
  • What's the point of `Hello()`? It doesn't do anything. Just call `HelloImpl` directly. Also, there's no point in making a method `async` just so you can await one task that you return; just make the method not be `async` and return that task directly. – Servy Dec 16 '13 at 21:56
  • @Servy I could make a complex implementation and all, but I prefered to put a very simple example to understand exactly what is the problem here. But if you want a more complex implementation of exactly that look at asp.net identity framework that is used exactly like that. – Erick Dec 16 '13 at 22:00
  • @Erick I'm describing how you can simplify your code, not telling you to make it more complex... – Servy Dec 16 '13 at 22:01

2 Answers2

8

The crux of the issue is that await will (by default) capture the current "context" and use that to resume the async method. In ASP.NET, that "context" is a SynchronizationContext that only allows one thread in at a time.

So, when you block the request thread by calling Result, you are blocking a thread within that SynchronizationContext and thus the Hello method cannot resume within that request context.

The reason your Hello2().Result works is that it's not actually an async method; it's just sticking some work on a thread pool thread, which will complete fine independently of the request thread.

I have a blog entry that goes into the full details.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
1

This is a deadlock. In SyncMethod you are waiting on yourself:

Hello().Result waits on your request's thread.

await HelloImpl() and the await Task.Run(...) inside of HelloImpl() both return execution to your request's thread, but can't because the .Result is blocking it.

Cory Nelson
  • 29,236
  • 5
  • 72
  • 110