Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I run async code as synchronous [duplicate]

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");
        }
    }
}
like image 363
Erick Avatar asked Dec 20 '25 19:12

Erick


2 Answers

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.

like image 75
Stephen Cleary Avatar answered Dec 22 '25 09:12

Stephen Cleary


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.

like image 40
Cory Nelson Avatar answered Dec 22 '25 09:12

Cory Nelson



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!