Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't marshal async continuations to the captured context #19

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

NetherGranite
Copy link

Currently, async/await continuations and Task.ContinueWith(...) continuations are marshalled back to the captured context (TaskScheduler or SynchronizationContext) as is the default behavior with these tools. I believe there is no reason for this in this library. An example of this is if I use this library in an ASP.NET Core web project currently, this library will post its continuations to the web framework's single-threaded UI synchronization context, resulting in worse performance and even deadlocks if asynchronous code is blocked on.

This resolves the issue by 1) using Task.ConfigureAwait(false) in async/await code to instruct the async state machines to schedule continuations onto the default thread pool and 2) passing TaskScheduler.Default to Task.ContinueWith(...) calls to ensure the continuations are run on the default thread pool.

Notably, Task.Run(...) schedules work onto the default thread pool by default, so no changes are needed there. Only continuations are subject to this "marshalling onto the captured context" behavior by default.

Resolves #17.

@mqudsi
Copy link
Member

mqudsi commented Jul 1, 2024

Thanks for this PR. I'll take a look at this later and see how it works under the stress test.

Note that ASP.NET Core does not have the behavior you are talking about, the synchronization context is in desktop .NET GUI apps and legacy ASP.NET (not Core).

@NetherGranite
Copy link
Author

Of course!

I'll take a look at this later and see how it works under the stress test.

It's very possible this will somehow perform worse under stress test as I didn't test it, but these changes instruct async state machines and the TPL to simply use TaskScheduler.Default rather than spending the extra time to discover what the currently captured task scheduler (or synchronization context in the case of async state machines) is and using that. This can be seen in ConfiguredTaskAwaitable, which executes a Task's continuation with a booleean parameter that skips the "current context" logic and goes straight to the thread pool, and in Task.ContinueWith(Action<Task>), which simply defers to the overload that takes a task scheduler and passes TaskScheduler.Current.

Note that ASP.NET Core does not have the behavior you are talking about

Sorry, I was referring to ASP.NET Core web projects with a UI aspect. For example, Blazor has a synchronization context, which is my current use case. Most if not all UI frameworks have one if I understand correctly.

@NetherGranite NetherGranite changed the title Don't marshal async continuations to the capture context Don't marshal async continuations to the captured context Jul 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ConfigureAwaits
2 participants