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

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.

Related

any kind of async operation freezes

I was trying to get the source of a web page async, and actually this is what I have done thousands of time before. it is working when it is not async, but it freezes whenever I try to make it async.
I tried:
await new WebClient().DownloadStringAsyncTask();
using WebRequest inside
await Task.Run(() => {
});
using new WebClient().DownloadString() inside
await Task.Run(() => {
});
using HttpClient.GetAsync();
and they were all failing. when I traced inside Task.Run() I noticed the the operation actually works, gets the result and completes however after completion it never returns. I tested even with empty Task.Run() just like below:
await Task.Run(() =>{
});
and still it doesn't return? is it a bug in .Net Framework 4.8? why a simple code that I have used thousands of times stop working?
By the way, It is a Winforms Application.
The code calling into this async code is blocking on this task's completion (via .Result or .Wait()). The Winforms Task Scheduler is assuming that your task needs to continue on the same thread it started from, but that thread is waiting for your Task to complete, putting you in a deadlock.
One solution is to remove the blocking code upward in your call stack, and go "async all the way". (It sounds like this will require refactoring async logic out of your constructor, which I highly recommend anyway).
Another would be to use .ConfigureAwait(false) consistently before all of your awaits that don't need to continue on the same thread they started from.
Another would be to put the Task.Run(...) call into the part of the code that's blocking, so it explicitly blocks on a different thread.

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.

Can this "Synchronous" Code Cause Deadlocks?

My company has a Nuget Package they wrote that can do various common tasks easily for you. One of which is making HTTP requests. Normally I always make my HTTP requests asynchronous, however in this Nuget package is the following code:
protected T GetRequest<T>(string requestUri)
{
// Call the async method within a task to run it synchronously
return Task.Run(() => GetRequestAsync<T>(requestUri)).Result;
}
Which calls this function:
protected async Task<T> GetRequestAsync<T>(string requestUri)
{
// Set up the uri and the client
string uri = ParseUri(requestUri);
var client = ConfigureClient();
// Call the web api
var response = await client.GetAsync(uri);
// Process the response
return await ProcessResponse<T>(response);
}
My question is, is this code really running synchronously by just wrapping the GetRequestAsync(requestUri) inside a Task.Run and calling .Result on the returned task? This seems like a deadlock waiting to happen, and we are seeing issues in areas of our app that utilize this function when running at higher loads.
Accessing Task.Result will block the current thread until the Task is complete, so it is not asynchronous.
As for deadlocks, this shouldn't happen, as Task.Run uses another thread for GetRequestAsync, which is not being blocked by the call to Result.
The reason that will not cause a deadlock is because Task.Run will push the delegate to be executed in a threadpool thread. Threadpool threads has no SynchronizationContext therefore no deadlock happens as there is no sync context to lock on between the async method GetRequestAsync and the caller. Same as you could have called .Result directly on the actual async method as well within Task.Run() block and that would not have caused a deadlock either.
Very inefficient though as you freeze 1 thread ie. 1 Core in the CPU do nothing but wait for the async method and I/O calls within it to complete. That s probably why you see a freeze in high load scenarios.
If you have a sync/async deadlock issue due to capturing sync context and blocking on async call, the deadlock will happen irrespective of load on a single call..
This won't cause a deadlock. But it's surely a resources wasting as one of the threads may be blocked.
The deadlock though may be possible if GetRequest looked like this:
protected T GetRequest<T>(string requestUri)
{
var task = GetRequestAsync<T>(requestUri);
return task.Result;
// or
// return GetRequestAsync<T>(requestUri).Result;
}
In example above you can see that I call GetRequestAsync within the current thread. Let's give the thread a number 0. Consider this line from the GetRequestAsync - await client.GetAsync(uri). .GetAsync is executed by a thread 1. After .GetAsync is done, default task scheduler is trying to return execution flow to the thread that executed the line - to the thread 0. But the thread that executed the line (0) is blocked right now as after we executed GetRequestAsync(), we are blocking it (thread 0) with task.Result. Hence our thread 0 remains blocked as it cannot proceed with execution of GetRequestAsync after await client.GetAsync(uri) is done nor it can give us the Result.
It's a pretty common mistake and I suppose you meant this one when asked about the deadlock. Your code is not causing it because you are executing GetRequestAsync from within another thread.

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.

async/await. Where is continuation of awaitable part of method performed?

I am really curious how async/await enables your program not to be halted.
I really like the way how Stephen Cleary explains async/await: "I like to think of "await" as an "asynchronous wait". That is to say, the async method pauses until the awaitable is complete(so it waits), but the actual thread is not blocked (so it's asynchornous)."
I've read that async method works synchronously till compilator meets await keywords. Well. If compilator cannot figure out awaitable, then compilator queues the awaitable and yield control to the method that called method AccessTheWebAsync. OK.
Inside the caller (the event handler in this example), the processing pattern continues. The caller might do other work that doesn't depend on the result from AccessTheWebAsync before awaiting that result, or the caller might await immediately. The event handler is waiting for AccessTheWebAsync, and AccessTheWebAsync is waiting for GetStringAsync. Let's see an msdn example:
async Task<int> AccessTheWebAsync()
{
// You need to add a reference to System.Net.Http to declare client.
HttpClient client = new HttpClient();
// GetStringAsync returns a Task<string>. That means that when you await the
// task you'll get a string (urlContents).
Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
// You can do work here that doesn't rely on the string from GetStringAsync.
DoIndependentWork();
// The await operator suspends AccessTheWebAsync.
// - AccessTheWebAsync can't continue until getStringTask is complete.
// - Meanwhile, control returns to the caller of AccessTheWebAsync.
// - Control resumes here when getStringTask is complete.
// - The await operator then retrieves the string result from getStringTask.
string urlContents = await getStringTask;
// The return statement specifies an integer result.
// Any methods that are awaiting AccessTheWebAsync retrieve the length value.
return urlContents.Length;
}
Another article from msdn blog says that async/await does not create new thread or use other threads from thread pool. OK.
My questions:
Where does async/await execute awaitable code(in our example downloading a web site) cause control yields to the next row of code of our program and program just asks result of Task<string> getStringTask? We know that no new threads, no thread pool are not used.
Am I right in my silly assumption that CLR just switches the current executable code and awaitable part of the method between each other in scope of one thread? But changing the order of addends does not change the sum and UI might be blocked for some unnoticeable time.
Where does async/await execute awaitable code(in our example downloading a web site) cause control yields to the next row of code of our program and program just asks result of Task getStringTask? We know that no new threads, no thread pool are not used.
If the operation is truly asynchronous, then there's no code to "execute". You can think of it as all being handled via callbacks; the HTTP request is sent (synchronously) and then the HttpClient registers a callback that will complete the Task<string>. When the download completes, the callback is invoked, completing the task. It's a bit more complex than this, but that's the general idea.
I have a blog post that goes into more detail on how asynchronous operations can be threadless.
Am I right in my silly assumption that CLR just switches the current executable code and awaitable part of the method between each other in scope of one thread?
That's a partially true mental model, but it's incomplete. For one thing, when an async method resumes, its (former) call stack is not resumed along with it. So async/await are very different than fibers or co-routines, even though they can be used to accomplish similar things.
Instead of thinking of await as "switch to other code", think of it as "return an incomplete task". If the calling method also calls await, then it also returns an incomplete task, etc. Eventually, you'll either return an incomplete task to a framework (e.g., ASP.NET MVC/WebAPI/SignalR, or a unit test runner); or you'll have an async void method (e.g., UI event handler).
While the operation is in progress, you end up with a "stack" of task objects. Not a real stack, just a dependency tree. Each async method is represented by a task instance, and they're all waiting for that asynchronous operation to complete.
Where is continuation of awaitable part of method performed?
When awaiting a task, await will - by default - resume its async method on a captured context. This context is SynchronizationContext.Current unless it is null, in which case it is TaskScheduler.Current. In practice, this means that an async method running on a UI thread will resume on that UI thread; an async method handling an ASP.NET request will resume handling that same ASP.NET request (possibly on a different thread); and in most other cases the async method will resume on a thread pool thread.
In the example code for your question, GetStringAsync will return an incomplete task. When the download completes, that task will complete. So, when AccessTheWebAsync calls await on that download task, (assuming the download hasn't already finished) it will capture its current context and then return an incomplete task from AccessTheWebAsync.
When the download task completes, the continuation of AccessTheWebAsync will be scheduled to that context (UI thread, ASP.NET request, thread pool, ...), and it will extract the Length of the result while executing in that context. When the AccessTheWebAsync method returns, it sets the result of the task previously returned from AccessTheWebAsync. This in turn will resume the next method, etc.
In general the continuation (the part of your method after await) can run anywhere. In practice it tends to run on the UI thread (e.g. in a Windows application) or on the thread pool (e.g. in an ASP .NET server). It can also run synchronously on the caller thread in some cases ... really it depends on what kind of API you're calling and what synchronization context is being used.
The blog article you linked does not say that continuations are not run on thread pool threads, it merely says that marking a method as async does not magically cause invocations of the method to run on a separate thread or on the thread pool.
That is, they're just trying to tell you that if you have a method void Foo() { Console.WriteLine(); }, changing that to async Task Foo() { Console.WriteLine(); } doesn't suddenly cause an invocation of Foo(); to behave any differently at all – it'll still be executed synchronously.
If by "awaitable code" you mean the actual asynchronous operation, then you need to realize that it "executes" outside of the CPU so there's no thread needed and no code to run.
For example when you download a web page, most of the operation happens when your server sends and receives data from the web server. There's no code to execute while this happens. That's the reason you can "take over" the thread and do other stuff (other CPU operations) before awaiting the Task to get the actual result.
So to your questions:
It "executes" outside of the CPU (so it's not really executed). That could mean the network driver, a remote server, etc. (mostly I/O).
No. Truly asynchronous operations don't need to be executed by the CLR. They are only started and completed in the future.
A simple example is Task.Delay which creates a task that completes after an interval:
var delay = Task.Delay(TimeSpan.FromSeconds(30));
// do stuff
await delay;
Task.Delay internally creates and sets a System.Threading.Timer that will execute a callback after the interval and complete the task. System.Threading.Timer doesn't need a thread, it uses the system clock. So you have "awaitable code" that "executes" for 30 seconds but nothing actually happens in that time. The operation started and will complete 30 seconds in the future.

Categories

Resources