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
Sorting a List Using Lambda/Linq to Objects
An Attempt Was Made to Access a Socket in a Way Forbidden by Its Access Permissions. Why
Showing a Windows Form on a Secondary Monitor
Why Use Simple Properties Instead of Fields in C#
Parsing JSON Object with Variable Properties into Strongly Typed Object
How to Get Intptr from Byte[] in C#
Convert JSON String to JSON Object C#
Is Accessing a Variable in C# an Atomic Operation
How to Get the Index of an Element in an Ienumerable
How to Connect to an .Mdf (Microsoft SQL Server Database File) in a Simple Web Project
C# Image Resizing to Different Size While Preserving Aspect Ratio
Pass-Through Mouse Events to Parent Control
How to Deserialize a Complex JSON Object in C# .Net
Databinding to List - See Changes of Data Source in Listbox, Combobox
The Need for Volatile Modifier in Double Checked Locking in .Net