0

I'm not sure if I'm even asking the question correctly, so bear with me; here's what I'm dealing with:

In my MVC4 project (targetting .Net 4.5.1) If I do await SomeAsyncMethod(...), then the task completes in the background but appears to never return. I believe this has something to do with the thread being returned to the pool and then resuming on a different thread. The workaround I've been using is to use Thread.Run(() => SomeTask).Result;.

So, I find myself having to do Thread.Run(() => SomeAsyncMethod).Result; a lot in my MVC projects lest I end up with deadlocks. Isn't this just another syntax for running the Task synchronously? I'm not sure if this is a limitation of MVC 4 (versus MVC 5) or if that's just how the api works. Am I essentially gaining nothing in terms of asynchronicity by doing this?

We've written a small library here where all of the operations are async Task<T> and it is in a separate assembly, so at least we can use it "properly" elsewhere (e.g. a window phone app), but this MVC 4 project is a consumer of said library, and it feels like we're basically stepping around the benefits of async/await in order to avoid deadlocks, so I'm looking for help in seeing the bigger picture here. It would help to better understand what I'm gaining by consuming asynchronous tasks in a synchronous mannger (if anything), what I'm losing, if there's a solution that gives me back the ability to await these tasks without deadlocking, and whether or not the situation is different between MVC 4 and MVC 5+

TIA

Sinaesthetic
  • 11,426
  • 28
  • 107
  • 176
  • You may want to look at the answer (and comments) in http://stackoverflow.com/questions/13489065/best-practice-to-call-configureawait-for-all-server-side-code – Jcl Sep 04 '15 at 17:54

3 Answers3

3

In my MVC4 project (targetting .Net 4.5.1) If I do await SomeAsyncMethod(...), then the task completes in the background but appears to never return.

This is almost certainly due to one of two things. Either:

  1. Code further up the call stack is calling Result or Wait on a task. This will cause a deadlock in ASP.NET. The correct solution is to replace Result/Wait with await. I have more details on my blog.
  2. The httpRuntime@targetFramework is not set to 4.5 or higher in your web.config. This is a common scenario for ASP.NET projects upgraded from an earlier version; you need to explicitly set this value for await to work correctly. There are more details in this blog post.

So, I find myself having to do Thread.Run(() => SomeAsyncMethod).Result; a lot in my MVC projects lest I end up with deadlocks. Isn't this just another syntax for running the Task synchronously?

Pretty much. What actually happens is that SomeAsyncMethod is run on a thread pool thread and then the request thread is blocked until that method is complete.

Am I essentially gaining nothing in terms of asynchronicity by doing this?

Correct. In fact, you're netting a negative benefit.

The whole point of asynchrony on the server side is to increase scalability by freeing up the request threads whenever they aren't needed. The Task.Run(..).Result technique not only prevents the request thread from being freed, it also uses other threads to do the actual work. So it's worse than just doing it all synchronously.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • I see that the web.config did not add that attribute to the httpRuntime tag. I'm adding it now and I'll try awaiting again. I have 0 instances of Task.Wait or Task.Run().Result behind the ones I've mentioned. Hopefully it start behaving correctly – Sinaesthetic Sep 04 '15 at 20:25
  • That appears to have fixed it! I am in process of factoring out all of the wrapper methods. Brilliant. Thanks! – Sinaesthetic Sep 04 '15 at 21:58
  • 1
    @Sinaesthetic: You're welcome! FYI, if you create a *new* MVC4/.NET4.5 project, it should set the `web.config` value correctly; this is only a problem for projects that have been *upgraded*. – Stephen Cleary Sep 04 '15 at 22:50
  • Side question -- has it been your experience that this situation where I'm using async/await, having forgot to set the targetFramework in the web.config (after upgrading to 4.5.1) could cause cpu spikes? We just encountered this in production and I'm looking into probable causes. – Sinaesthetic Sep 08 '15 at 17:12
  • 1
    @Sinaesthetic: I haven't seen that specifically, but it's possible. Using `async` without `targetFramework` has undefined behavior, so technically anything can happen. – Stephen Cleary Sep 08 '15 at 17:26
0

Addition to @Stephen's answer, my 2 cents:

I believe this has something to do with the thread being returned to the pool and then resuming on a different thread.

Edit: No, it all happens on single thread(usually UI thread, in case of MVC it's thread allocated for the request). Async await work on Message pump which is an infinite loop running on single thread. Each await puts a message on message pump and checks if it finished.

Above is not exactly applied for Asp.net. See @Jeff's comments below.

=========================================================

One rule of Async framework, If it's Async, keep it Async all the way.

Creating synchronous wrapper over Async method often results in blocking Main threads where Main thread and Task thread keep on waiting for each other to respond.

vendettamit
  • 14,315
  • 2
  • 32
  • 54
  • This is incorrect. await by itself does nothing. Resuming control to the code after await is handled by a synchronization context which must decide what thread to allocate, be it the thread pool, the UI thread, or something else. Using async actions in WebAPI V5, which is async aware does not resume on the original thread. It continues the request, potentially on a different thread – Jeff Sep 04 '15 at 19:55
  • May be I'm out of sync here. I need to revisit the Async await specs. This is I knew since the time of Asyc await released. Or may be I couldnt express the idea really well. – vendettamit Sep 04 '15 at 20:03
  • 1
    Async and await is just a compiler trick that emits calls to the TPL using Tasks and Awaiters. A framework must implement a SychronizationContext to ensure that control is resumed in a suitable manner upon awaiting – Jeff Sep 04 '15 at 20:06
  • I agree a SynchronizationContext is needed to come back to where the async call was made with await. In Asp.net MVC we have AspNetSynchronizationContext which is used to return to the caller. In WebApi we really do not have a Synchronization context or do we? Which is why we need to explicity implement and use a custom context for tasks which are using ContinueWith feature so that they'll return to origin thread otherwise default Context will be used i.e. ThreadPool that could endup resuming on another thread. I'm not sure what made you think it's `incorrect`. – vendettamit Sep 04 '15 at 20:43
  • It still doesn't necessarily come back on the same thread, even with a SynchronizationContext. In ASP .NET, the appropriate behavior is not to use a message pump like a client application does for the UI. – Jeff Sep 04 '15 at 21:16
  • And yes, WebAPI still requires a SynchronizationContext to function correctly upon awaiting. https://msdn.microsoft.com/en-us/library/hh975440(v=vs.120).aspx – Jeff Sep 04 '15 at 21:18
  • Interesting facts! Thanks for sharing. +1. – vendettamit Sep 04 '15 at 21:19
  • Specifically .NET 4.5 and higher relies on https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.configureawait(v=VS.110).aspx – Jeff Sep 04 '15 at 21:23
0

ASP .NET MVC 4 should be aware of the async keyword on Actions and should correctly handle resuming the request when using the await keyword. Are you sure the Action in question that uses await returns a Task and doesn't try to return T itself?

This is handled by ASP .NET MVC using a SynchronizationContext to ensure that the request is resumed correctly after awaiting even if it is on a different thread.

And yes, if you just call .Result, it's blocking the calling thread until the Task completes and you end up using (potentially) two threads for the same request without any benefit.

http://www.asp.net/mvc/overview/performance/using-asynchronous-methods-in-aspnet-mvc-4

Jeff
  • 35,755
  • 15
  • 108
  • 220