What happens when awaiting a call on a different thread - c#

Let's say I invoke a Task like this:
Task.Factory.StartNew(async () =>
{
await SomeAction();
}, TaskCreationOptions.LongRunning);
and inside SomeAction there's a line like this:
await OtherAction();
with a regular await, the code goes back to the invoking context, but here it's a task running on a different thread. So what happens with the context of the await there? Is it actually like blocking in this case?

with a regular await, the code goes back to the invoking context, but here it's a task running on a different thread. So what happens with the context of the await there? Is it actually like blocking in this case?
Here's how await works. await never blocks.
await will first examine its awaitable (usually a task); if the awaitable is already complete, then await will extract the result (or exception) and just continue executing the method (synchronously). If the awaitable is not already complete, then await will behave asynchronously.
When await behaves asynchronously, it will (by default) capture the current "context" (SynchronizationContext.Current or, if that is null, then TaskScheduler.Current), and then return an incomplete task. Since the example code is using Task.Factory.StartNew without specifying a TaskScheduler (which IMO is always a bug), then its delegate was executed on whatever TaskScheduler.Current was at the point StartNew was called. So the context that will be captured by the await is that same TaskScheduler.Current.
If TaskScheduler.Current was the same as TaskScheduler.Default (which is likely but by no means guaranteed), then when the async method returns, the thread is returned to the thread pool, and since StartNew doesn't understand async lambdas, the Task returned from StartNew is completed. This means the LongRunning flag in there just makes things slower; it's the opposite of optimization.
Later, when the await completes, it will resume executing the lambda on the captured context (TaskScheduler.Current). If TaskScheduler.Current was the same as TaskScheduler.Default, then the lambda will resume executing on a thread newly acquired from the thread pool (possibly the same thread but likely a different one).
This sounds crazy complicated, but really that's only because the code is using StartNew. If you look at the code using Task.Run, it's much simpler because the context is always TaskScheduler.Default (the thread pool context):
Task.Run(async () =>
{
await SomeAction();
});
Now you can say with certainty that if the await acts asynchronously, then it will return a task, returning the thread to the thread pool. When SomeAction completes, then the lambda will continue executing on a thread pool thread. Also, since Task.Run understands async lambdas, the task returned from Task.Run will not complete until the lambda is complete.
Here's some guidelines:
NEVER, EVER, EVER use Task.Factory.StartNew without passing a TaskScheduler. This is a 100%-of-the-time, always-on rule.
For asynchronous code, use Task.Run instead of Task.Factory.StartNew.

Related

What is the difference between discarding DoAwait() VS Task.Run(() => DoAwait())

So let's assume I have the following async method:
public async Task DoAsync()
{
await Task.Delay(10000);
ExecuteContinuation();
}
Now I want to execute this task in a fire-and-forget way from ASP.NET (non-Core) Controller method (I know that's not the best idea, but that's not the scope of the question).
Therefore I simply discard returned task:
_ = DoAsync();
That leads to an interesting result - ExecuteContinuation() is never executed, breakpoint is never hit.
Now if I wrap it in Task.Run, everything works just fine:
_ = Task.Run(() => DoAsync());
Question is - why such behavior happens, and why ExecuteContinuation() is never executed without Task.Run?
As far as I remember, Task.Run forces the task to execute on a thread pool ignoring SynchronizationContext, but does it make any difference, considering that I never await returned task?
Another thing - _ = DoAsync(); works as expected if I do same with ASP.NET Core. I suppose because there is no SynchronizationContext, but again - I don't know why that matters in this case.
As far as I remember, Task.Run forces the task to execute on a thread pool ignoring SynchronizationContext, but does it make any difference, considering that I never await returned task?
Yes. It makes a difference for the await inside DoAsync. When calling it directly, it will capture the current SynchronizationContext (representing the current request). Later when that await resumes executing, it will attempt to resume on the captured context for the request that has already completed. The resulting behavior is undefined.
When calling DoAsync from Task.Run, no context is captured and DoAsync may resume executing on any thread pool thread without the context for the completed request.
Note that since this is fire-and-forget, it is inevitable that DoAsync will occasionally not finish, even when using Task.Run. Your system as a whole must properly handle that scenario. The proper solution for fire-and-forget code is a distributed architecture.

ConfigureAwait(false) doesn't make the continuation code run in another thread

I have a code which runs on thread1.
I call a function in a synchronic way (using async method but it shouldn't disturb me - async method doesn't turn code to be asynchronic).
I have an await with ConfigureAwait set to false, so I understood the
code after it is a task continuation which suppose to run in a different thread than the code before the await (because ConfigureAwait was set to false).
By my test - All code run in the same thread.
How is it? Why doesn't the code, below the await, run on a different thread?
This is the code:
public async void F1()
{
Console.WriteLine($"Thread.CurrentThread.ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
int x = await F2().ConfigureAwait(false);
Console.WriteLine($"Thread.CurrentThread.ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
}
private async Task<int> F2()
{
Console.WriteLine("Begins F2");
Console.WriteLine($"Thread.CurrentThread.ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine("Finishes F2");
return 7;
}
This is the output:
Thread.CurrentThread.ManagedThreadId=1
Begins F2
Thread.CurrentThread.ManagedThreadId=1
Finishes F2
Thread.CurrentThread.ManagedThreadId=1
It is not "supposed to run in a different thread context", it is allowed to. In the opposite case (ConfigureAwait(true)) it must continue on the same thread context.
Furthermore when there is nothing to await (inside the method), the "async" method runs synchronously so doesn't need to return to some thread context, it is still on it.
I have an await with ConfigureAwait set to false, so I understood the code after it is a task continuation which suppose to run in a different thread than the code before the await (because ConfigureAwait was set to false).
No. There are a couple of misunderstandings here.
The first misunderstanding is regarding what ConfigureAwait(false) does.
ConfigureAwait (and await) have nothing to do with threading directly. By default, await captures a context - the current SynchronizationContext or TaskScheduler. This context could resume on the same thread (e.g., UI SynchronizationContexts commonly do this), but "context" does not necessarily mean "thread" (e.g., the ASP.NET pre-Core SynchronizationContext can resume on any thread pool thread).
What ConfigureAwait(false) actually does is skip capturing that context. So the thread pool context is used, which may run on any thread pool thread. Note that if the code before await was running on a thread pool thread, it may resume on any thread pool thread, including the thread that it was running on before.
The second misunderstanding is regarding when ConfigureAwait(false) is applied.
await will first check to see if its awaitable is complete, and only then will it actually behave asynchronously. So if you await an already-completed task, the ConfigureAwait(false) is never even considered - the code just continues running synchronously.
How can I enforce it to run on a different thread then?
Use Task.Run. Task.Run is the proper tool to use when you need to run code on a thread pool thread.

Does calling an async method immediately running its commands

I got this code:
static async Task AsynchronousProcessing()
{
Task<string> t1 = GetInfoAsync("Task 1", 3);
Task<string> t2 = GetInfoAsync("Task 2", 5);
string[] results = await Task.WhenAll(t1, t2);
foreach (string result in results)
{
WriteLine(result);
}
}
static async Task<string> GetInfoAsync(string name, int seconds)
{
await Task.Delay(TimeSpan.FromSeconds(seconds));
return "hi";
}
When reaching the line Task<string> t1 = GetInfoAsync("Task 1", 3);, will it already start asynchronously with the await Task.Delay(TimeSpan.FromSeconds(seconds)); and move on to the rest of the method, or will it return immediatly a task without actually starting it?
And if it won't start when calling the method, will the tasks start only when calling the Task.WhenAll(t1,t2)? Since if it doesn't start right away, I would expect to see GetInfoAsync doing something like return new Task<string>(() => ...);
I recommend reading my async intro.
In summary, all async methods begin executing synchronously. So when AsynchronousProcessing calls GetInfoAsync, then GetInfoAsync begins running, synchronously, just like any other method. An async method can become asynchronous when there is an await. In this case, GetInfoAsync calls Task.Delay (again, synchronously, just like a regular method call), and then passes its Task to await. At this point, await will examine the task; if it is already complete, then it will continue running synchronously; otherwise, it will act asynchronously and return an incomplete task from GetInfoAsync.
The tasks returned from async methods are in progress. I don't usually use the term "running", because they are not actually running code on a CPU and because the task status is not actually Running. This "asynchronous in-progress" state is unfortunately named WaitingForActivation (even though it's not waiting for anything).
Since if it doesn't start right away, I would expect to see GetInfoAsync doing something like return new Task(() => ...);
The async keyword handles the creation of the task. As noted above, the tasks are "hot" or "in progress". This is not the same as "running" or "started", both of which imply tasks that run CPU code. There are two types of tasks: what I call Delegate Tasks and Promise Tasks. The ones returned from async methods are Promise Tasks.
If there aren't any new threads with await, then how will they run in parallel?
They run concurrently (not in parallel, since there is no additional thread blocked on the delay).
However, when the await Task.Delay(..) completes, then there could possibly be another thread used. The await will resume on its captured context and execute the return "hi"; in that context. If the captured context is a thread pool context, then that one line of code will be executed on a thread pool thread. The async state machine translates the return into code that completes the task that was previously returned from that async method.
GetInfoAsync is called and runs synchronously just as usual until it hits the await Task.Delay(TimeSpan.FromSeconds(seconds)) line. It then returns an uncompleted task back to the calling AsynchronousProcessing() method.
So, yes, the task is indeed being started when you call the method, i.e. you don't have to await GetInfoAsync for the Task.Delay method to get called.

Is there a way to apply .ConfigureAwait(false) to a whole task subtree?

I have a class that runs thousands of async tasks in parallel which is much greater than amount of worker threads. If I don't do .ConfigureAwait(false) on await calls then I get much lower performance but attaching .ConfigureAwait(false) to every single await call is tedious and reduces code readability. I'm looking for a way to specify some kind of null context in a function that spawns those tasks so that every await call inside them automatically doesn't care about SynchronizationContext. Is it possible?
Update: I've done some googling and it looks like once I'm already inside a function with .ConfigureAwait(false) the current SynchronizationContext will be null and I don't need to care about it in child function calls. Is that correct?
Yes, you can. By resetting the current SynchronizationContext.
At the top level async method do this:
async Task TopLevelAsync()
{
var syncContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
try
{
// no need for ConfigureAwait(false)
await SubTask1Async();
await SubTask2Async();
}
finally
{
SynchronizationContext.SetSynchronizationContext(syncContext);
}
}
once I'm already inside a function with .ConfigureAwait(false) the current SynchronizationContext will be null and I don't need to care about it in child function calls. Is that correct?
Unfortunately, it's not. It's a dangerous assumption. And here is why:
// in a top-level async method..
await FooAsync().ConfigureAwait(false);
async Task FooAsync()
{
var result = await BarAsync().ConfigureAwait(false);
// we are inside the ConfigureAwait(false), or in the
// continuation after ConfigureAwait(false), so at this
// point the SynchronizationContext must be null, right?
// No it's not.
}
Task<bool> BarAsync()
{
return Task.FromResult(true);
}
From the example above the BarAsync completes synchronously, that means the state machine behind the FooAsync method continues it's execution immediately without re-scheduling. Without re-scheduling logic, the ConfigureAwait(false) does not taken into an account, so the SynchronizationContext is never reset.
it looks like once I'm already inside a function with .ConfigureAwait(false) the current SynchronizationContext will be null and I don't need to care about it in child function calls. Is that correct?
Well, the actual situation is:
You're inside an asynchronous function.
You start a task.
You do an await (configured with false) on that task before it completes.
So, it's not a great situation to depend on. Specifically, if the task completes quickly (before your configured await is hit), then you will still be on the original context. Note that this can happen even if you don't expect it to; for example, mobile devices are extremely aggressive about caching web requests.
To "step out" of the synchronization context, just wrap it in a Task.Run - this uses a thread pool thread very briefly, and executes all the descendant code in that thread pool context.

is using an an `async` lambda with `Task.Run()` redundant?

I just came across some code like:
var task = Task.Run(async () => { await Foo.StartAsync(); });
task.Wait();
(No, I don't know the inner-workings of Foo.StartAsync()). My initial reaction would be get rid of async/await and rewrite as:
var task = Foo.StartAsync();
task.Wait();
Would that be correct, or not (again, knowing nothing at all about Foo.StartAsync()). This answer to What difference does it make - running an 'async' action delegate with a Task.Run ... seems to indicate there may be cases when it might make sense, but it also says "To tell the truth, I haven't seen that many scenarios ..."
Normally, the intended usage for Task.Run is to execute CPU-bound code on a non-UI thread. As such, it would be quite rare for it to be used with an async delegate, but it is possible (e.g., for code that has both asynchronous and CPU-bound portions).
However, that's the intended usage. I think in your example:
var task = Task.Run(async () => { await Foo.StartAsync(); });
task.Wait();
It's far more likely that the original author is attempting to synchronously block on asynchronous code, and is (ab)using Task.Run to avoid deadlocks common in that situation (as I describe on my blog).
In essence, it looks like the "thread pool hack" that I describe in my article on brownfield asynchronous code.
The best solution is to not use Task.Run or Wait:
await Foo.StartAsync();
This will cause async to grow through your code base, which is the best approach, but may cause an unacceptable amount of work for your developers right now. This is presumably why your predecessor used Task.Run(..).Wait().
Mostly yes.
Using Task.Run like this is mostly used by people who don't understand how to execute an async method.
However, there is a difference. Using Task.Run means starting the async method on a ThreadPool thread.
This can be useful when the async method's synchronous part (the part before the first await) is substantial and the caller wants to make sure that method isn't blocking.
This can also be used to "get out of" the current context, for example where there isn't a SynchronizationContext.
It's worth noting that your method has to be marked async to be able to use the await keyword.
The code as written seems to be a workaround for running asynchronous code in a synchronous context. While I wouldn't say you should never ever do this, using async methods is preferable in almost every scenario.
// use this only when running Tasks in a synchronous method
// use async instead whenever possible
var task = Task.Run(async () => await Foo.StartAsync());
task.Wait();
Async methods, like your example of Foo.StartAsync(), should always return a Task object. This means that using Task.Run() to create another task is usually redundant in an async method. The task returned by the async method can simply be awaited by using the await keyword. The only reason you should use Task.Run() is when you are performing CPU-bound work that needs to be performed on a separate thread. The task returned by the async method can simply be awaited by using the await keyword. You can read more in depth details in Microsoft's guide to async programming.
In an async method, your code can be as simple as:
await Foo.StartAsync();
If you want to perform some other work while the task is running, you can assign the function to a variable and await the result (task completion) later.
For example:
var task = Foo.StartAsync();
// do some other work before waiting for task to finish
Bar();
Baz();
// now wait for the task to finish executing
await task;
With CPU-bound work that needs to be run on a separate thread, you can use Task.Run(), but you await the result instead of using the thread blocking Task.Wait():
var task = Task.Run(async () => await Foo.StartAsync());
// do some other work before waiting for task to finish
Bar();
Baz();
// now wait for the task to finish executing
await task;

Categories

Resources