Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make ASP.Net MVC Controller Action Async

My desire is to make an MVC controller Action which does long-running i/o operations asynchronous. My goal is to avoid tying up threads in the ASP.Net thread pool while this long-running method completes.

The Action makes two calls.

The first call is to a 3rd party dll that does not contain any asynchronous methods. This dll reads from a proprietary database and does fairly complex cpu-bound processing. It can take up to a couple of seconds to return.

The second call uses the results of the first call as parameters that are passed to a database query using Entity Framework.

Simplified, this is the Action:

public async Task<ActionResult> MyActionAsync(arg1, arg2)
{
    var parameters = 3rdPartyComponent.TakesLongTime(arg1, arg2);

    Task<List<MyClass>> genericList = null;

    using (DbContexts.MyDbContext db = new DbContexts.MyDbContext())
        {
          genericList = await db.Database.SqlQuery<MyClass>(sql,parameters).ToListAsync();
        }

    return View("MyView", genericList);
}

I would like to make the call to 3rdPartyComponent awaitable. My initial idea was to do this:

var parameters = await Task.Run(() => 3rdPartyComponent.TakesLongTime()).ConfigurateAwait(false);

but I've read several subject matter experts state categorically that using Task.Run() inside an asp.net MVC Action is counter-productive and should never be done.

3rdPartyComponent is a black box of compiled code, it cannot be altered to add async methods.

Is there any way to make the call to 3rdPartyComponent awaitable, so that the entire Action runs without tying up a thread in the asp.net thread pool?

like image 328
Tom Regan Avatar asked Oct 15 '15 15:10

Tom Regan


People also ask

Can a controller method be async?

As a quick reminder, you can make any existing controller method asynchronous by changing it to return a Callable. For example a controller method that returns a view name, can return Callable<String> instead.

How do you call async method in MVC action?

STEP 01 Create new MVC Application project, named as "Async". In the File menu, click New Project. In the "New Project" dialog box, under Project types, expand Visual C#, and then click "Web". In the Name box, type "Async", then click on Ok.

Should I use async controller actions?

Calling an asynchronous controller action will not block a thread in the thread pool. Asynchronous actions are best when your method is I/O, network-bound, or long-running and parallelizable. Another benefit of an asynchronous action is that it can be more easily canceled by the user than a synchronous request.


2 Answers

The first call is to a 3rd party dll that does not contain any asynchronous methods. This dll reads from a proprietary database and does fairly complex cpu-bound processing.

I would like to make the call to 3rdPartyComponent awaitable.

Is there any way to make the call to 3rdPartyComponent awaitable, so that the entire Action runs without tying up a thread in the asp.net thread pool?

The code is already as good as it can get. Neither Task.Run nor Task.Factory.StartNew will give you any benefit (even if you pass the LongRunning flag).

Since the 3rd-party dll does CPU-bound code, it needs a thread. Even if you could modify it, you could only make the db access (I/O work) asynchronous; CPU work is synchronous by definition, and from your description it sounds like the CPU-bound work is the majority of the time.

The entire point about avoiding Task.Run (and the even worse Task.Factory.StartNew) on ASP.NET is that they cause less efficient behavior. By freeing up the ASP.NET request thread, you're just returning a thread to the thread pool while it's not being used; there's nothing magical or special about the ASP.NET request threads. So Task.Run frees up one thread pool thread by switching to another thread pool thread, and Task.Factory.StartNew with LongRunning frees up a thread pool thread by creating a whole new thread, scheduling the work to that, and then tearing down that thread at the end (this behavior is not documented nor specified, but it is the current observed behavior).

So what you end up doing is just incurring needless thread switches (and in the case of StartNew, an entire extra thread). ASP.NET is designed to handle both synchronous and asynchronous work; if you have synchronous code, the best thing to do is just execute it directly, exactly like your code is already doing.

like image 57
Stephen Cleary Avatar answered Nov 14 '22 21:11

Stephen Cleary


Yes, I'd argue that using Task.Run in an async server method is an anti-pattern because the work that you schedule would simply end up being requeued in the ThreadPool, which is exactly the place you just came from... net gain zero (minus the overhead of scheduling a callback in the ThreadPool).

My first thoughts are to fire off the work with

Task.Factory.StartNew(action,TaskCreationOptions.LongRunning)

TaskCreationOptions.LongRunning is a hint that the work should be done in a new Thread rather than the ThreadPool.

...but if this is a heavy traffic method, you run the risk of creating threads faster than they clear down and now you've lost the management benefits of the ThreadPool.

If the traffic really is as high as you say and requires this sort of special treatment, some sort of throttling and/or cacheing might be in order... But that would be a different question...

like image 34
spender Avatar answered Nov 14 '22 21:11

spender