0

In this article, we can see there's a SynchronizationContextAwaiter implementation for C# runtime to check if SynchronizationContext.Current is desired one, this enable us to jump between threads when writing async methods.

My question is how does SynchronizationContext.Current change. Does SynchronizationContext rotates? Or does it get picked randomly?

EDIT: To be more specific, how does IAsyncStateMachine.MoveNext gets executed by different SynchronizationContext?

Ren
  • 79
  • 7
  • foreground/background threads are a different concept than UI threads/non-UI threads. Be aware of the difference – Thomas Weller Aug 30 '21 at 06:41
  • @ThomasWeller Good point, I'll edit it to just "jump between threads". – Ren Aug 30 '21 at 06:49
  • Excellent read here: https://devblogs.microsoft.com/pfxteam/await-synchronizationcontext-and-console-apps/ – Moho Aug 30 '21 at 07:05
  • 1
    Be aware that the technique presented in [the article](https://thomaslevesque.com/2015/11/11/explicitly-switch-to-the-ui-thread-in-an-async-method/) for offloading work to a background thread, is flawed. The `ConfigureAwait(false);` has no effect in case the awaitable is completed at the `await` point, so if the `Task` returned by the `GetDataFromRemoteServerAsync()` happens to complete very fast, it is theoretically possible that the `DoSomeCpuBoundWorkWithTheData(data);` **will run on the UI thread**. The correct and reliable way to offload work to the `ThreadPool` is the `Task.Run` method. – Theodor Zoulias Aug 30 '21 at 07:37
  • @Moho Yeah that's a good article, but it does not cover about how IAsyncStateMachine.MoveNext is executed by different contexts. – Ren Aug 30 '21 at 07:45
  • @TheodorZoulias Thanks for the heads up, I'll keep that in mind. The question is more about to `move between threads`, so it won't be a problem if a context is specified (with an awaiter). – Ren Aug 30 '21 at 07:48
  • You may find [this question](https://stackoverflow.com/questions/15363413/why-was-switchto-removed-from-async-ctp-release "Why was “SwitchTo” removed from Async CTP / Release?") interesting. Some people like switching from context to context imperatively, most don't (or don't know that it's even possible). – Theodor Zoulias Aug 30 '21 at 07:51
  • @TheodorZoulias I believe it's quite useful in Unity, which loops on magic methods with certain names. – Ren Aug 30 '21 at 07:56
  • 4
    This question is a little disjointed. What is it you are actually trying to achieve here, what problem are you trying to solve? It would be likely easier than than a deep dive into the internal plumbing of the async and await pattern – TheGeneral Aug 30 '21 at 08:42
  • @TheGeneral I can already jump to desired thread with the SynchronizationContextAwaiter approach. But the question, `How does this works?` just occurs to me every now and then. – Ren Aug 30 '21 at 09:08

1 Answers1

2

How does C# decide which SyncronizationContext to check on whether an awaiter is completed

When code awaits a task, it first checks whether the task is complete. If it's already complete, then the code continues executing synchronously past the await. Otherwise, by default await will capture the SynchronizationContext.Current at that time. This default behavior is prevented by using ConfigureAwait(continueOnCapturedContext: false).

this enable us to jump between threads when writing async methods.

True. But I don't recommend that style of code. IMO the code is more maintainable by using separate methods that each run in a specific context. Or if you don't want to use separate methods, at least split them up using delegates.

E.g., if you want to run code on a UI thread, it's cleaner to queue a method to run on that UI thread rather than have one method that is partially run on a background thread and partially run on a UI thread. This is especially true when considering error handling: if you have a try that is partially one thread and partially another, you also have to assume the catch/finally blocks can run in either of those contexts.

My question is how does SynchronizationContext.Current change.

SynchronizationContext.Current can be set by any thread, but it's usually set by whatever queueing mechanism is executing code. E.g., UI threads get their SynchronizationContext.Current set when they enter their main loop (which includes their queue), i.e., Dispatcher.Run for WPF or Application.Run for WinForms.

EDIT: To be more specific, how does IAsyncStateMachine.MoveNext gets executed by different SynchronizationContext?

The SynchronizationContext is captured by the TaskAwaiter, and when the awaitable completes, it resumes executing the method by passing the continuation to SynchronizationContext.Post (unless it can execute it directly).

All of this is open source, BTW.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thanks for the advice, and yes I value good way of writing code, It's just that I believe this is useful in Unity. Not sure if I follow, does this answers the core question `how does IAsyncStateMachine.MoveNext gets executed by different SynchronizationContext?`? About `when the awaitable completes`, AsyncStateMachine checks that by looking at the TaskAwaiter's `IsCompleted`, at this point, who is running it? – Ren Aug 31 '21 at 00:28
  • `IsCompleted` is executed by the same thread that was running before the `await`. – Stephen Cleary Aug 31 '21 at 00:31
  • If that is true, I can't get my head around how is it possible to jump between threads by await until `SynchronizationContext.Current` is the desired one. – Ren Aug 31 '21 at 00:33
  • 1
    If `IsCompleted` is false, then the state machine schedules a continuation on the awaitable. This continuation is a `SynchronizationContextAwaitTaskContinuation`, which executes the continuation by passing it to `SynchronizationContext.Post`. – Stephen Cleary Aug 31 '21 at 00:40
  • 1
    Oh my! I thought OnCompleted is executed when it is completed. Really should have pay more attention... Sorry for the bothering and thanks very much! – Ren Aug 31 '21 at 00:46