How to Use Non-Thread-Safe Async/Await APIs and Patterns with ASP.NET Web API

How to use non-thread-safe async/await APIs and patterns with ASP.NET Web API?

Entity Framework will (should) handle thread jumps across await points just fine; if it doesn't, then that's a bug in EF. OTOH, OperationContextScope is based on TLS and is not await-safe.

1. Synchronous APIs maintain your ASP.NET context; this includes things such as user identity and culture that are often important during processing. Also, a number of ASP.NET APIs assume they are running on an actual ASP.NET context (I don't mean just using HttpContext.Current; I mean actually assuming that SynchronizationContext.Current is an instance of AspNetSynchronizationContext).

2-3. I have used my own single-threaded context nested directly within the ASP.NET context, in attempts to get async MVC child actions working without having to duplicate code. However, not only do you lose the scalability benefits (for that part of the request, at least), you also run into the ASP.NET APIs assuming that they're running on an ASP.NET context.

So, I have never used this approach in production. I just end up using the synchronous APIs when necessary.

Realizing the benefits of async/await in a Web API when a syncronous method is in the mix


So is there a way to realize the benefits of async/await in a Web API scenario when there is a synchronous operation in the mix?

No. The reason is simple: asynchrony provides its benefits by releasing its thread, and a synchronous operation blocks a thread.

The benefit of asynchrony on the server side is scalability. Asynchrony provides better scalability because it releases its thread when it's not needed.

A synchronous API blocks a thread, by definition. If the API is an I/O-bound operation, then it's blocking a thread that is mainly just waiting for I/O, i.e., the thread isn't needed but it also can't be released.

There's no way around this; the synchronous API must block a thread, so you cannot get the benefits of asynchrony.

If you had a GUI application, then there is a workaround. On the client side the primary benefit of asynchrony is responsiveness. Asynchrony provides better responsiveness by releasing the UI thread when it's not needed.

For GUI applications, you can use Task.Run to block a thread pool thread, and your UI thread remains responsive; this is an appropriate workaround. On server applications, using Task.Run like that is an antipattern, since it forces a thread switch and then you still end up with a blocked thread anyway (preventing any scalability benefit), so Task.Run just slows down your code for no benefit at all.

What would be the memory consumption difference if we use non-async instead of async for asp.net/web api?

The CLR does not create new threads whenever all the thread pool threads are blocked. It grows the thread pool "reluctantly", and within bounds.

See

Beginning with the .NET Framework 4, the thread pool creates and
destroys worker threads in order to optimize throughput, which is
defined as the number of tasks that complete per unit of time. Too few
threads might not make optimal use of available resources, whereas too
many threads could increase resource contention.

The managed thread pool

Is there any benefits using async in terms of memory consumption?

Perhaps. If you have a lot of concurrent requests that are waiting on back-end resources, async allows you to handle those with fewer threads. And fewer threads means less memory for thread stacks. However the concurrent requests themselves consume memory. So if the thread pool reduces the number of concurrent requests it can also reduce the amount of memory used.

ASP.NET and async - how it works?


Is this described behaviour right ?

Yes.

Is it correct ?

Yes.

is the DoSomeHeavyWork processed inside task1 or where (where it is
"awaited") ? I think this a key question.

From the current code, DoSomeHeavyWork will asynchronously wait for Task.Delay to complete. Yes, this will happen on the same thread allocated by the thread-pool, it won't spin any new threads. But there isn't a guarantee that it will be the same thread, though.

which thread will continue to process the request after await?

Because we're talking about ASP.NET, that will be an arbitrary thread-pool thread, with the HttpContext marshaled onto it. If this was WinForms or WPF app, you'd be hitting the UI thread again right after the await, given that you don't use ConfigureAwait(false).

request produces thread allocating from the thread pool, but response
will not be sent until the DoSomeHeavyWorkAsync finished and it
doesn't matter in which thread this method executes. In other words,
according to single request and single concrete task (DoSomeHeavyWork)
there is no benefits of using async. Is it correct ?

In this particular case, you won't see the benefits of async. async shines when you have concurrent requests hitting the server, and alot of them are doing IO bound work. When using async while hitting the database, for example, you gain freeing the thread-pool thread for the amount of time the query executes, allowing the same thread to process more requests in the meanwhile.

But how IO completion thread calls back thread pool thread in this
case ?

You have to separate parallelism and concurrency. If you need computation power for doing CPU bound work in parallel, async isn't the tool that will make it happen. On the other hand, if you have lots of concurrent IO bound operations, like hitting a database for CRUD operations, you can benefit from the usage of async by freeing the thread while to IO operation is executing. That's the major key point for async.

The thread-pool has dedicated pool of IO completion threads, as well as worker threads, which you can view by invoking ThreadPool.GetAvailableThreads. When you use IO bound operations, the thread that retrieves the callbacks is usually an IO completion thread, and not a worker thread. They are both have different pools.

Can code after 'await' run in different thread in ASP.NET?

For ASP.NET Classic (.NET Framework), there is a special AspNetSynchronizationContext, the continuation will post back to the original context thread.

ASP.NET Core there isn’t one. If you inspect SynchronizationContext.Current you’ll find that it’s set to null. As such, a continuation is free to use what ever thread it chooses, and will suffer no classic deadlocks in that respect


Update

Some great corrections from @StephenCleary in the comments

Minor correction : on classic ASP.NET, the SynchronizationContext
represents the request context, not a specific thread.

The method may resume on any thread pool thread after the await.
The deadlock occurs because there is a lock as part of that request
context
to ensure that only one thread at a time may be in the
request context.

So, when the async method is ready to resume, a thread pool thread
is taken which enters the request context and tries to take that
lock. If there's another thread blocked on that task in the context, the lock is already taken and a deadlock will occur

EF6 Async Methods Confusion

From the same EF docs you quoted:

For the moment, EF will detect if the developer attempts to execute
two async operations at one time and throw.

So, this code should work even if there's a thread switch after await, because it's still executed sequentially:

var dbContext = new DbContext();
var something = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
var morething = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 2);

At least, this is how it is expected to work. If sequential execution still produces a threading-related exception, this should be reported as an EF bug.

On the other hand, the following code will most likely fail, because we introduce parallelism:

var dbContext = new DbContext();
var somethingTask = dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
var morethingTask = dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 2);

await Task.WhenAll(somethingTask, morethingTask);

var something = somethingTask.Result;
var morething = morethingTask.Result;

You need to make sure you don't use the same DbContext with more than one pending EF operation.

Updated, the 1st code fragment actually works fine with EF v6.1.0, as expected.



Related Topics



Leave a reply



Submit