Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async/Await in multi-layer C# applications

I have a multi-layered C# MVC4 web application in a high-traffic scenario that uses dependency injection for various repositories. This is very useful because it is easily testable, and in production we can easily configure the repositories for specific controllers. All controllers inherit from AsyncController, so action methods that return Task<JsonResult> or Task<ActionResult> truly help our server scale with more users and solve for the dreaded thread starvation problem. Some repositories use web services, which we can definitely use Async/Await to provide scalability benefits. In other cases, some may use unmanaged services which can't be threaded safely, in which async/await will provide no benefits. Each repository is an implement of an interface, IRepository. In short, each implementation is drastically different in the way it may get data. This configuration is chosen at deployment-time with web.config changes and Autofac modules.

What are some of the recommended ways to implement async/await in an application such as this? To make the pattern fit in my existing application, I have to change the interface to return Task<MyBusinessObject>. But isn't this more of an implementation detail? I could provide two method stubs, GetData and GetDataAsync, but to me that wouldn't allow the flexibility I have now with IoC frameworks like Autofac where I can easily swap everything out without having to change code.

One of the Microsoft developers for Async/Await has published a blog post, "Should I expose an asynchronous wrapper for my synchronous methods?" that sort of goes into the problem. If your async method isn't truly async (providing native I/O benefits) then it really only adds overhead. In other words, it won't provide any scalability benefits.

I just would like how the rest of the Community has tackled this problem.

like image 291
Tim P. Avatar asked May 21 '13 18:05

Tim P.


2 Answers

In your situation, I recommend making your interfaces asynchronous:

public interface IMyInterface
{
  Task<TMyResult> GetDataAsync();
}

For synchronous methods, you can return Task.FromResult:

public class MyImplementation : IMyInterface
{
  public Task<TMyResult> GetDataAsync()
  {
    TMyResult ret = ...;
    return Task.FromResult(ret);
  }
}

Asynchronous methods can of course be async and use await.

Regarding the implementation details, I'd say "yes and no". ;) It's very similar to IDisposable in this regard. Logically, it is an implementation detail in the sense that it shouldn't matter which way it's implemented. It's not an implementation detail in the sense that the calling code needs to handle it differently. So I would agree that - logically - it is an implementation detail, but that the .NET platform is not sufficiently advanced to treat it as such.

Alternatively, you can think of it like this (which is the explanation I usually give): async is the actual implementation detail, and when you return Task<T> instead of T, you're defining an interface where the implementation may be asynchronous.

I have a series of blog posts that address designing with async "in the large" (i.e., fitting async in with OOP rather than allowing it to be functional). I address some common problems such as asynchronous initialization and IoC. It's just one person's opinion, but I'm not aware of any other resources for these kinds of problems.

P.S. You don't need to inherit from AsyncController anymore in MVC4. The default controller handler will automatically detect async methods by their return type (Task or Task<T>).

like image 52
Stephen Cleary Avatar answered Nov 05 '22 23:11

Stephen Cleary


Task is a leaky abstraction. If you wish to benefit the use of IO completion port threads, you will have to return Tasks all the way from your repositories to your controllers.

Although making the whole call stack return Task<T> might improve scalability, it does add an extra layer of complexity to your application. This will hinder maintainability, testability and makes it harder to reasoning about code. Asynchronous code will always be harder than synchronous code.

Personally, I rather increase the amount of memory of the server and configure the application to have a bigger thread pool to compensate the loss of scalability, or scale out to a few extra (virtual) servers, instead of adding this extra complexity.

like image 36
Steven Avatar answered Nov 05 '22 21:11

Steven