3

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
S Itzik
  • 494
  • 3
  • 12
  • Your code is not actually async. Try at least add `await Task.Delay(...)` or `Task.Yield` to `F2` – Guru Stron May 22 '20 at 12:54
  • 1
    Aside to the question, `async void` is an anti-pattern and should not be used in most cases: https://markheath.net/post/async-antipatterns – Martin Costello May 22 '20 at 12:56

3 Answers3

8

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.

Hans Kesting
  • 38,117
  • 9
  • 79
  • 111
  • What do you mean "It is not supposed to run in a different thread, it is allowed to" ?? How can I enforce it to run on a different thread then? – S Itzik May 22 '20 at 13:01
  • 2
    It doesn't have to continue on the same thread, it has to continue in the same context. E.g. the classic ASP.Net synchronization context doesn't guarantee which thread you'll get, just that it has exclusive access to things like the request. – Damien_The_Unbeliever May 22 '20 at 13:01
7

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Ok I definitely don't know the difference between thread and context. I'm about to read this article you wrote in https://learn.microsoft.com/he-il/archive/msdn-magazine/2011/february/msdn-magazine-parallel-computing-it-s-all-about-the-synchronizationcontext in order to understand this topic. Would you say I need to read something better for this goal? – S Itzik May 22 '20 at 13:28
  • @SItzik: That's a fine article if you want to dive deep specifically into `SynchonizationContext`, which is one of the two context types `await` can use. If you want to learn more about what this answer discusses, I recommend [the blog post I linked to](https://blog.stephencleary.com/2012/02/async-and-await.html) and follow up with [Don't Block on Async Code](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html) and [async best practices](https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming). – Stephen Cleary May 22 '20 at 13:49
  • I've read these articles you linked to (All the 3). Thank you. Still one q remained open. If the context is the UI, then configureAwait(false) will change the context to the Task scheduler, If the context is Asp.Net - then the same, configureAwait(false) will change the context to the Task scheduler. Now, if the context was in the beginning the Task scheduler, then what will configureAwait(flase) change the context to? The answer can not be "another thread pool" because another thread pool means the same context - The Task scheduler. – S Itzik May 24 '20 at 10:31
  • @SItzik: `ConfigureAwait(false)` does not change the context; it avoids it completely and uses the thread pool context. So it skips *both* the `SynchronizationContext` and the `TaskScheduler`. – Stephen Cleary May 24 '20 at 16:04
  • Interesting, I will quote you with your permission: "The context is the current SynchronizationContext, or the current TaskScheduler if the current SynchronizationContext is null." This is from https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html. This sentence means there are only 2 options for the context: a) current SynchronizationContext. b)TaskScheduler. There is no "thread pool" context. UI thread context and ASP.NET are both type of SynchronizationContext, so the only option remained is that the thread pool context you mentioned is actually a TaskScheduler. – S Itzik May 25 '20 at 10:08
  • @SItzik: There are other task schedulers besides the thread pool one. – Stephen Cleary May 25 '20 at 17:31
0

The naming is very unfortunate but it's actually easy to explain.

  • If you don't use ConfigureAwait(false) you basically say the opposite ConfigureAwait(true) which tells the runtime that you want to proceed on the same context as you were called from.
  • By using ConfigureAwait(false) to tell the runtime that you don't need to proceed on the same context as you were called from - but it's up to the runtime to decide which context makes sense then, so it's likely to be another but it does not have to be this way.

Microsoft could have made this easier by using different methods like these for example:

// context is 'A'
await Do().KeepContext();
// context is 'A'

// context is 'A'
await Do().FreeContext();
// context is 'A' but could also be 'B' or 'C'

Taken from my article https://github.com/awaescher/ObviousAwait

Waescher
  • 5,361
  • 3
  • 34
  • 51
  • Instead of the mystified `KeepContext`/`FreeContext` I would prefer the more expressive and accurate `ContinueOnCapturedContext`/`ContinueSynchronously`. – Theodor Zoulias Jun 22 '23 at 13:07
  • I think it is hard to do `ContinueOnCapturedContext()` for the opposite case, like my best guess would be `ContinueOnAnyContext()`. Also `ContinueSynchronously()` is misleading as the whole method is very likely to be executed asynchronously already. – Waescher Jun 22 '23 at 13:10
  • I think that the name `ContinueSynchronously` is consistent with the existing option [`TaskContinuationOptions.ExecuteSynchronously`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcontinuationoptions). And this is what actually does. It doesn't continue on "any context" (what does this even mean?). It continues on the same thread that completes the task. It's up to the implementation of the asynchronous method, not up to the Runtime. – Theodor Zoulias Jun 22 '23 at 13:16
  • Compare also with the setting [`ChannelOptions.AllowSynchronousContinuations`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.channels.channeloptions.allowsynchronouscontinuations). – Theodor Zoulias Jun 22 '23 at 13:17
  • > what does this even mean? -- literally any context, the same as the method was called with or any other context. And context here is an abstraction for things, like threads, etc. which every `SynchronizationContext?` can define on its own. Context is common here, like the boolean argument of `ConfigureAwait(...)` which is `continueOnCapturedContext`. – Waescher Jun 22 '23 at 14:18
  • Can you show me a native .NET API that is named `AnyContext`, or its name is composed partially of this term? I don't know of any, and I find the concept of *"any context"* confusing. It raises questions like "how many contexts as there?", "who choses one?", "with what criteria is it chosen?" etc. It makes things sound more complicated than they really are. – Theodor Zoulias Jun 22 '23 at 14:30
  • [Here](https://dotnetfiddle.net/XP44GF) is a demo that shows that the `await` continues on the thread that completed the task. In this case is a newly created thread. The task is not completed on the `ThreadPool`, so the continuation after the `await` doesn't run on the `ThreadPool`. I posted this demo last year in a comment under Serg's answer [here](https://stackoverflow.com/questions/70728287/await-operator-in-c-sharp-spanning-a-background-thread/70728555#comment125040208_70728555). – Theodor Zoulias Jun 22 '23 at 14:40
  • > "how many contexts as there?", "who choses one?", "with what criteria is it chosen?" -- that's the thing the SynchronizationContext handles for you, why would you care? Any context means literally any context that is available and not just the one that was used while entering the method. I don't understand how this can bother you so much, you don't have to use it either. – Waescher Jun 23 '23 at 05:57
  • I am providing feedback to your answer. If my feedback is not welcome, just ask and I'll delete my comments. – Theodor Zoulias Jun 23 '23 at 06:34