We have a service layer in our application which is composed of three logical layers - web service, business model services (our name for the layer that executes business logic and orchestrates calls to various repositories), and the repository layer which connects to various DBs using EF6.
Many of our repository calls just get data straight from DB sets via ToListAsync, FirstOrDefaultAsync, like this:
public async Task<MyObject> GetSomeData()
{
using(var context = new myDBContext())
{
return await context.SomeDbSet.FirstOrDefault(o=>o.Something == true);
}
}
We're having a bit of an internal debate as to whether using await here is the right thing to do or not, because there is nothing executing in this method after the await. I/we understand that the way the code is written, it is a necessity otherwise the context would be disposed of as soon as the method exists and it would result in an exception. But if we await here, we have to await all the way up (or down, depending on how you look at it) our call stack, and that would result in a number of expensive and somewhat unnecessary context switches.
The other option here is to make the repository methods synchronous, and do a Task.Run() in the method that calls the repository method, like:
Task.Run(() => MyRepository.GetSomeData());
we can then await this call if we want, or just return the task object again to the caller. The downside here is the call to the database then becomes synchronous and one thread from the pool is blocking for the entire length of the database call.
So this comes down to what's more expensive? Unnecessary context switches via await or having threads block? It seems that there is no right answer, but is there a best practice?
Any thoughts would be appreciated.
You should, of course, use the async version.
As you said, if you don't await you will dispose of the context before the operation completed, but that doesn't mean the calling methods need to use async-await as well. They can return the task just as you mention in the Task.Run option:
public Task<MyObject> FooAsync()
{
// do some stuff
return GetSomeDataAsync();
}
public async Task<MyObject> GetSomeDataAsync()
{
using(var context = new myDBContext())
{
return await context.SomeDbSet.FirstOrDefault(o=>o.Something == true);
}
}
You mentioned that the cost in this case is some expensive context switches. I'm not sure what you mean by that, but if you're referring to thread context switches then there's only a single one. The calling thread will be released while awaiting the asynchronous operation and a different thread will continue running when that operation completes.
Not only that this is negligible compared to the time it takes to execute the actual operation, if you use Task.Run you have the same context switch as a blocked thread is taken out of the CPU.
Using Task.Run on a synchronous operation is redundant. It's just blocking a thread and it potentially requires more context switches then the async equivalent.
There are many kinds of “context” in the .NET Framework: LogicalCallContext, SynchronizationContext, HostExecutionContext, SecurityContext, ExecutionContext etc.. SynchronizationContext is captured when using Async/Await, but it is no the only context that get captured. Along with SynchronizationContext, ExecutionContext also get captured. ExecutionContext consist of SecurityContext, LogicalCallContext etc..
An async code is always executed against the captured ExecutionContext. When an await complete, if there was a current SynchronizationContext that got captured, the continuation representing the remainder of the asynchronous method is posted to that SynchronizationContext.
So when executing code under Task.Run, it's only the SynchronizationContext that will no get captured, but ExecutionContext will still get captured in any case. You can get same behaviour of not getting SynchronizationContext captured by async/await using ConfigureAwait(false) when awaiting. The down side is that when await completes, the SynchronizationContext will be ignored and the Framework will attempt to continue the execution wherever the previous asynchronous operation completed, which is what you exactly want.
So in your scenarios I think you should be using async/await with ConfigureAwait(false), as there would not be any overhead of SynchronizationContext in this case and at the same time there would be no overhead of blocking any thread.
The following post may be helpful to get more insight: https://msdn.microsoft.com/en-us/magazine/hh456402.aspx
Related
I was reading this article and found this example:
public static class DeadlockDemo
{
private static async Task DelayAsync()
{
await Task.Delay(1000);
}
// This method causes a deadlock when called in a GUI or ASP.NET context.
public static void Test()
{
// Start the delay.
var delayTask = DelayAsync();
// Wait for the delay to complete.
delayTask.Wait();
}
}
The root cause of this deadlock is due to the way await handles contexts. By default, when an incomplete Task is awaited, the current “context” is captured and used to resume the method when the Task completes. This “context” is the current SynchronizationContext unless it’s null, in which case it’s the current TaskScheduler. GUI and ASP.NET applications have a SynchronizationContext that permits only one chunk of code to run at a time. When the await completes, it attempts to execute the remainder of the async method within the captured context. But that context already has a thread in it, which is (synchronously) waiting for the async method to complete. They’re each waiting for the other, causing a deadlock.
I understand that if there was code after await Task.Delay(1000);, it would be part of the continuation, and that continuation would be running in the same context as Test(), and that's how the deadlock would happen.
However, there is no continuation, so how does the deadlock actually happen?
Or, is there an empty continuation that is created? What would be the point in that?
This is the part that is confusing me:
When the await completes, it attempts to execute the remainder of the
async method within the captured context.
What is the "remainder of the async method"?
If we're an async Task or async Task<T> method, then there's always work to do after the await - we need to ensure that any exception produced by the awaited task is properly propagated to our own Task, or that we pass the appropriate normal return value through.
If we're any kind of async method using structures such as using which insert compiler-generated code at the end of an otherwise appearing empty epilogue to our method, then we'll be inserting code at the end of the method, even if it doesn't appear in the source.
If we're any normal async method that liberally uses awaits then we'll already have a state machine built and running to execute our method and there's no point in optimizing the "no code after the last await" possibility.
In the narrow case that we're an async void1 method that contains a single await at the end, then other than some minutiae about where an unhandled exception from a task might be reported, we already had the opportunity to avoid excess code generation by not making the method async at all and just ignoring the awaitable.
So there's no reason to try to optimize this situation.
1We're already in a state of sin at that point anyway.
I'm trying to find out which approach is better let's say we have a button, after user clicks it we perform 1. Send a async request using httpClient
2. Some heavy synchronous staff like computations and saving data to a database.
Like that:
button1.Click += async(sender, e) =>
{
bool a = await Task.Run(async () => { return await MyTask1();});
}
async Task<bool> MyTask1()
{
await new HttpClient().GetAsync("https://www.webpage.com");
DoHeavyStuffFor5minutes();
return true;
}
button2.Click += async(sender, e) =>
{
bool a = await MyTask2();
}
async Task<bool> MyTask2()
{
await new HttpClient().GetAsync("https://www.webpage.com").ConfigureAwait(false);
DoHeavyStuffFor5minutes();
}
From what i understand GetAsync does not block my UI thread because underneath it uses a method which make it runs on different thread perhaps Task.Run or any other method that allows that.
But DoHeavyStuffFor5Minutes will block my UI because it will get called on the caller SynchornizationContext.
So i read that using ConfigureAwait(false) will make code after GetAsync do not run on the same SynchornizationContext as the caller. My question is, which approach is better first or the second one?
There is no need to execute HttpClient.GetAsync on a background thread using Task.Run since the HTTP request is truly asynchronous by nature so in this case your second approach is better that the first one.
When the Task returned by GetAsync has eventually finished, the remainder or MyTask2() will be executed on a thread pool thread assuming you opt out of capturing the context by calling ConfigureAwait(false).
Note however that ConfigureAwait(false) does not guarantee that the callback or remainer won't be run in the original context in all cases.
From Stephen Toub's blog post:
Does ConfigureAwait(false) guarantee the callback won’t be run in the original context?
"No. It guarantees it won’t be queued back to the original contex...but that doesn’t mean the code after an await task.ConfigureAwait(false) won’t still run in the original context. That’s because awaits on already-completed awaitables just keep running past the await synchronously rather than forcing anything to be queued back. So, if you await a task that’s already completed by the time it’s awaited, regardless of whether you used ConfigureAwait(false), the code immediately after this will continue to execute on the current thread in whatever context is still current."
So you might want to off-load DoHeavysTuffFor5minutes, which I assume is a CPU-bound and potentially long-running operation, to a background thread using Task.Run to be on the safe side. At least in the general case.
Also note that a method that is named *Async and returns a Task or Task<T> might still block the calling thread depending on its implementation. In general, this may be a reason to use your first approach of a calling both methods on a background thread in order to avoid blocking the UI thread. If you however use well-implemented APIs, such as HttpClient, this isn't an issue though.
In an ASP.NET application, I have an action which when hit, starts a new background task in the following way:
Task.Factory.StartNew(async () => await somethingWithCpuAndIo(input), CancellationToken.None, TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning, TaskScheduler.FromCurrentSynchronizationContext());
I'm not awaiting it, I just want it to start, and continuing doing its work in the background.
Immediately after that, I return a response to the client.
For some reason though, after the initial thread executing the background work hits an await that awaits the completion of a method, when debugging, I successfully resolve the method, but upon returning, execution just stops and does not continue below that point.
Interestingly, if I fully await this task (using double await), everything works as expected.
Is this due to the SynchronizationContext? The moment I return a response, the synchronizationContext is disposed/removed? (The SynchronizationContext is being used inside the method)
If it is due to that, where exactly does the issue happen?
A) When the Scheduler attempts to assign the work on the given synchronizationContext, it will already be disposed, so nothing will be provided
B) Somewhere down the lines in the method executing, when I return a response to the client, the synchronizationContext is lost, regardless of anything else.
C) Something else entirely?
If it's A), I should be able to fix this by simply doing Thread.Sleep() between scheduling the work and returning a response. (Tried that, it didn't work.)
If it's B) I have no idea how I can resolve this. Help will be appreciated.
As Gabriel Luci has pointed out, it is due the the first awaited incomplete Task returning immediately, but there's a wider point to be made about Task.Factory.StartNew.
Task.Factory.StartNew should not be used with async code, and neither should TaskCreationOptions.LongRunning. TaskCreationOptions.LongRunning should be used for scheduling long running CPU-bound work. With an async method, it may be logically long running, but Task.Factory.StartNew is about starting synchronous work, the synchronous part of an async method is the bit before the first await, this is usually very short.
Here is the guidance from David Fowler (Partner Software Architect at Microsoft on the ASP.NET team) on the matter:
https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/86b502e88c752e42f68229afb9f1ac58b9d1fef7/AsyncGuidance.md#avoid-using-taskrun-for-long-running-work-that-blocks-the-thread
See the 3rd bulb:
Don't use TaskCreationOptions.LongRunning with async code as this will
create a new thread which will be destroyed after first await.
Your comments made you intentions a little clearer. What I think you want to do is:
Start the task and don't wait for it. Return a response to the client before the background task completes.
Make sure that the somethingWithCpuAndIo method has access to the request context.
But,
A different thread won't be in the same context, and
As soon as the first await is hit, a Task is returned, which also means that Task.Factory.StartNew returns and execution of the calling method continues. That means that the response is returned to the client. When the request completes, the context is disposed.
So you can't really do both things you want. There are a couple of ways to work around this:
First, you might be able to not start it on a different thread at all. This depends on when somethingWithCpuAndIo needs access to the context. If it only needs the context before the first await, then you can do something like this:
public IActionResult MyAction(input) {
somethingWithCpuAndIo(input); //no await
}
private async Task somethingWithCpuAndIo(SomeThing input) {
// You can read from the request context here
await SomeIoRequest().ConfigureAwait(false);
// Everything after here will run on a ThreadPool thread with no access
// to the request context.
}
Every asynchronous method starts running synchronously. The magic happens when await is given an incomplete Task. So in this example, somethingWithCpuAndIo will start executing on the same thread, in the request context. When it hits the await, a Task is returned to MyAction, but it is not awaited, so MyAction completes executing and a response gets sent to the client before SomeIoRequest() has completed. But ConfigureAwait(false) tells it that we don't need to resume execution in the same context, so somethingWithCpuAndIo resume execution on a ThreadPool thread.
But that will only help you if you don't need the context after the first await in somethingWithCpuAndIo.
Your best option is to still execute on a different thread, but pass the values you need from the context into somethingWithCpuAndIo.
But also, use Task.Run instead of Task.Factory.StartNew for reasons described in detail here.
Update: This can very likely cause unpredictable results, but you can also try passing a reference to HttpContext.Current to the thread and setting HttpContext.Current in the new thread, like this:
var ctx = HttpContext.Current;
Task.Run(async () => {
HttpContext.Current = ctx;
await SomeIoRequest();
});
However, it all depends on how you are using the context. HttpContext itself doesn't implement IDiposable, so it, itself, can't be disposed. And the garbage collector won't get rid of it as long as you're holding a reference to it. But the context isn't designed to live longer than the request. So after the response is returned to the client, there may be many parts of the context that are disposed or otherwise unavailable. Test it out an see what explodes. But even if nothing explodes right now, you (or someone else) might come back to that code later, try to use something else in the context and get really confused when it blows up. It could make for some difficult-to-debug scenarios.
Consider this example:
async Task Foo()
{
button.Text = "This is the UI context!";
await BarA();
button.Text = "This is still the UI context!";
await BarB();
button.Text = "Oh no!"; // exception (?)
}
async Task BarA()
{
await Calculations();
}
async Task BarB()
{
await Calculations().ConfigureAwait(false);
}
How do I know if calling await BarB() changes the context without reading the body of the async Task BarB() function? Do I really need to know exactly whether an async function calls ConfigureAwait(false) at any point? Or maybe the example is wrong and there's no exception?
What BarB() does is largely irrelevant, unless BarB() needs to talk to the UI. The important part here is the await in the line:
await BarB();
it is that await which captures context (too) - and negotiates getting back onto the correct context if it finds itself continuing from the wrong context. So; unless you write:
await BarB().ConfigureAwait(false);
you should be fine here.
The ConfigureAwait(false) in BarB() looks normal and is probably correct, if we assume that BarB() doesn't need to directly update the UI or perform any other context-bound operation.
To supplement Marc's answer - bear in mind that async is an implementation detail of a method. How or why a method creates a Task that it hands back to you are largely transparent (and that's why async isn't part of the signature, not allowed in interface definitions, etc)
Whilst it's true that your method shares it's "current context" with methods it calls, that's only to the extent that those methods, if they want to know the current context, will call SynchronizationContext.Current. They'll then use methods on that context to get "back on it" if necessary. Any "changes" to the context will usually actually be performed by switching continuations onto a suitable thread that already has the right context set.
What these methods won't normally do is call SetSynchronizationContext. It's only if a method does call that that you have to worry about changes to "your" context. (Some infrastructure code may call that method if their synchronization context is free-threaded but requires other ambient resources. They'll get a thread pool thread, call SetSynchronizationContext, execute the continuation and then unset the synchronization context again).
According to best practices it is recommended to use .ConfigureAwait(false) with async/await keywords if you can:
await Task.Run(RunSomethingAsync).ConfigureAwait(false);
Can you please give me an example of a situation when I cannot use .ConfigureAwait(false)?
You "cannot" use ConfigureAwait(false) when you actually care about the synchronization context you're in. For example, imagine the following in a GUI application:
public async void SomeButtonClick(object sender, EventArgs e)
{
var result = await SomeAsyncOperation().ConfigureAwait(false);
textBox.Text = result;
}
When you return from ConfigureAwait, you wont be back on the UI thread. This will cause an InvalidOperationException.
From the source: Asynchronous .NET Client Libraries for Your HTTP API and Awareness of async/await's Bad Effects:
When you are awaiting on a method with await keyword, compiler
generates bunch of code in behalf of you. One of the purposes of this
action is to handle synchronization with the UI (or main) thread. The key
component of this feature is the SynchronizationContext.Current which
gets the synchronization context for the current thread.
SynchronizationContext.Current is populated depending on the
environment you are in. The GetAwaiter method of Task looks up for
SynchronizationContext.Current. If current synchronization context is
not null, the continuation that gets passed to that awaiter will get
posted back to that synchronization context.
When consuming a method, which uses the new asynchronous language
features, in a blocking fashion, you will end up with a deadlock if
you have an available SynchronizationContext. When you are consuming
such methods in a blocking fashion (waiting on the Task with Wait
method or taking the result directly from the Result property of the
Task), you will block the main thread at the same time. When
eventually the Task completes inside that method in the threadpool, it
is going to invoke the continuation to post back to the main thread
because SynchronizationContext.Current is available and captured. But
there is a problem here: the UI thread is blocked and you have a
deadlock!