What is the lifetime of SetSynchronizationContext? - c#

I have a custom SynchronizationContext which abstracts away a custom work queue system. I'm trying to write a helper function which makes it easy for client code to write code for it.
My helper function currently looks like:
void async Task QueueTaskWithCustomContext(Func<Task> task) {
var oldContext = SynchronizationContext.Current;
try {
SynchronizationContext.SetCurrent(new CustomSynchronizationContext());
// Yielding prevents any inline synchronous work from being done outside the work queue.
await Task.Yield();
await task();
} finally {
SynchronizationContext.SetCurrent(oldContext);
}
}
And I expect to be used like:
await QueueTaskWithCustomContext(async() => {
await Something();
await SomethingElse();
});
I don't know if this is kosher but it seems to work for the test cases I've thrown at it. I'm a little nervous about how I'm setting the Synchronization context and how it might interact with asynchronous code, though. Specifically, are there any situations where the custom synchronization context could "leak" out of the helper function and be set for other tasks? I'm thinking if the helper function isn't awaited immediately. I couldn't make it happen after some quick experimentation but I'm still nervous.
Alternatively, is the way I've set up the try/finally block guaranteed to set the synchronization context even after the first await? Again, I couldn't make it happen from some quick experimentation but I'm still nervous about it.
I suppose I don't really understand the lifetime of setting the SynchronizationContext. Is it set forever, or until unset, or just for the current function or...?

Specifically, are there any situations where the custom synchronization context could "leak" out of the helper function and be set for other tasks?
Absolutely:
QueueTaskWithCustomContext(async() => await Task.Delay(1000));
await Task.Delay(10);
The smaller delay outside will run on your custom scheduler, not the default scheduler.
I wholeheartedly recommend against this kind of degenerate code, you're inviting nothing but trouble by surprising your users with something as basic as the task scheduler suddenly and unexpectedly changing.
Edit: Since I didn't explicitly answer your main question, the life time of the "current" task scheduler is the lifetime of the thread. It's stored in TLS so it will exist as long as your thread exists.

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.

Fire and forget, using `Task.Run` or just calling an async method without `await`

Note: This a question for outside of the realm of Asp.Net and web apps.
In general especially when it comes to libraries or console apps, in order to fire and forget an async method, is it better to just call the async method without awaiting it or use Task.Run?
Basically:
public static void Main(){
// Doing
_ = DoSomethingAsync();
// vs.
_ = Task.Run(DoSomethingAsync);
}
private static async Task DoSomethingAsync()
{
...
}
In general especially when it comes to libraries or console apps, in order to fire and forget an async method, is it better to just call the async method without awaiting it or use Task.Run?
In general, it's best not to use fire-and-forget at all.
"Fire and forget" means:
You don't care if the code has an exception. Any exceptions will cause it to fail silently; no logging, no notification, etc.
You don't need to know when the code completes. I.e., the consuming application doesn't ever need to wait for the code to complete. Even during shutdown.
You don't need the code to complete. As a corollary of (2), fire-and-forget code may not run to completion; and as a corollary of (1), you would have no notification that it failed to complete.
In short, "fire and forget" is only appropriate for an extremely small number of tasks. E.g., updating a cache. I'd say probably 85% or more of "fire and forget" code is wrong - it's using fire and forget for code that should not be fire and forget.
So I'd say the best solution is to not use fire and forget at all. At the very least, you should expose a Task somewhere that represents a "followup action". Consider adding the Task to your return type or exposing it as a property.
Adopting fire and forget - especially in a library - means you're forcing all consumers to never know when it's safe to shut down and exit. But if you really want to do fire and forget, there are a few options.
A. One option is calling an async void function without a context. The consuming application still has no way to determine if/when the code completes, but at least that way exceptions are not ignored.
B. Another option is to start the task without a context. This option has both disadvantages of fire and forget code: exceptions are ignored and the calling code cannot know when it completes.
Both of these recommendations start the task without a context. There are helpers for doing this, or you can wrap the call in Task.Run (slightly less efficient, but it works fine).
I wouldn't recommend starting the task directly. While this would work fine in a Console app, it's not appropriate for libraries which may be called in situations where a context is provided.
In general, it depends but Task.Run is the safer choice and the reasoning behind it depends on two things:
If we can guarantee that the async method is always going to be asynchronous. Guaranteeing a method to be always asynchronous can become especially tricky for library projects or if the async method is in a code that is not managed by the caller. For example once I had implemented method similar to this example in a library:
public async Task HandleMessages(Func<Task> onMessageNotification)
{
while(true)
{
var msg = MessagingClient.ReceiveMessage();
...
if(msg.MessageType == "MakeAPizza")
{
_ = onMessageNotification();
_ = MakePizzaAsync();
}
}
}
private async Task MakePizzaAsync() {...}
I wanted to fire and forget onMessageNotification and MakePizzaAsync since I didn't want it to hold up the message handling. What I didn't catch was when the caller implementedonMessageNotification like below:
Func<Task> onMsgNotification = () => {
DoSlowOperation();
return Task.CompletedTask;
}
Which would practically made the onMessageNotification synchronous operation, and had to wait on the DoSlowOperation() to finish. So in order to avoid to fix issue I just had to change it to:
_ = Task.Run(onMessageNotification);
_ = MakePizzaAsync();
We won't have long running operations in the async method before the first await operation. The other way that the caller can cause issues is if onMsgnotification Func were implemented in the following way:
Func<Task> onMsgNotification = async () => {
DoSlowOperation();
await AsyncOperation();
}
Which would have still slowed down the HandleMessages method because DoSlowOperation is still getting called synchronously.

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.

Can ConfigureAwait(false) in a library lose the synchronization context for the calling application?

I've read the advice many times from people smarter than me, and it has few caveats: Always use ConfigureAwait(false) inside library code. So I'm fairly certain I know the the answer, but I want to be 100%. The scenario is I have a library that thinly wraps some other asynchronous library.
Library code:
public async Task DoThingAsyc() {
// do some setup
return await otherLib.DoThingAsync().ConfigureAwait(false);
}
Application code:
// need to preserve my synchronization context
await myLib.DoThingAync();
// do I have my context here or did my lib lose it?
No.
The capturing of the SynchronizationContext happens on await. ConfigureAwait configures the specific await.
If the application calls a library's async method and awaits it the SC is captured on the spot regardless of what happens inside the call.
Now, because the async method's synchronous part (which is the part before the first await) is executed before a task is returned to be awaited, you can mess around with the SynchronizationContext there, but ConfigureAwait doesn't do that.
In your specific example you seem to be returning the result of ConfigureAwait from the async method. That can't happen because ConfigureAwait returns the ConfiguredTaskAwaitable struct. If however we change the method return type:
public ConfiguredTaskAwaitable DoThingAsyc()
{
return otherLib.DoThingAsync().ConfigureAwait(false);
}
Then awaiting it will indeed affect the calling code's await behavior.
Example from http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx
... logically you can think of the following code:
await FooAsync();
RestOfMethod();
as being similar in nature to this:
var t = FooAsync();
var currentContext = SynchronizationContext.Current;
t.ContinueWith(delegate
{
if (currentContext == null)
RestOfMethod();
else
currentContext.Post(delegate { RestOfMethod(); }, null);
}, TaskScheduler.Current);
which means you should have your context back after the await myLib.DoThingAync(); call.
If used inconsistently in the logical chain of async calls, ConfigureAwait(false) may add redundant context switches (which usually means redundant thread switches). This may happen in the presence of synchronization context, when some async calls on the logical stack use ConfigureAwait(false) and some don't (more here).
You still should use ConfigureAwait(false) in your code, but you may want to peek into the 3rd party code you're calling and mitigate any inconsistency with something like this:
public async Task DoThingAsyc() {
// do some setup
await Task.Run(() => otherLib.DoThingAsync()).ConfigureAwait(false);
// do some other stuff
}
This would add one extra thread switch, but might potentially prevent many others.
Moreover, if you're creating a really thin wrapper like you showed, you may want to implement it like below, without async/await at all:
public Task DoThingAsyc() {
// do some setup
return otherLib.DoThingAsync();
}

Why is TaskScheduler.Current the default TaskScheduler?

The Task Parallel Library is great and I've used it a lot in the past months. However, there's something really bothering me: the fact that TaskScheduler.Current is the default task scheduler, not TaskScheduler.Default. This is absolutely not obvious at first glance in the documentation nor samples.
Current can lead to subtle bugs since its behavior is changing depending on whether you're inside another task. Which can't be determined easily.
Suppose I am writting a library of asynchronous methods, using the standard async pattern based on events to signal completion on the original synchronisation context, in the exact same way XxxAsync methods do in the .NET Framework (eg DownloadFileAsync). I decide to use the Task Parallel Library for implementation because it's really easy to implement this behavior with the following code:
public class MyLibrary
{
public event EventHandler SomeOperationCompleted;
private void OnSomeOperationCompleted()
{
SomeOperationCompleted?.Invoke(this, EventArgs.Empty);
}
public void DoSomeOperationAsync()
{
Task.Factory.StartNew(() =>
{
Thread.Sleep(1000); // simulate a long operation
}, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default)
.ContinueWith(t =>
{
OnSomeOperationCompleted(); // trigger the event
}, TaskScheduler.FromCurrentSynchronizationContext());
}
}
So far, everything works well. Now, let's make a call to this library on a button click in a WPF or WinForms application:
private void Button_OnClick(object sender, EventArgs args)
{
var myLibrary = new MyLibrary();
myLibrary.SomeOperationCompleted += (s, e) => DoSomethingElse();
myLibrary.DoSomeOperationAsync(); // call that triggers the event asynchronously
}
private void DoSomethingElse() // the event handler
{
//...
Task.Factory.StartNew(() => Thread.Sleep(5000)); // simulate a long operation
//...
}
Here, the person writing the library call chose to start a new Task when the operation completes. Nothing unusual. He or she follows examples found everywhere on the web and simply use Task.Factory.StartNew without specifying the TaskScheduler (and there is no easy overload to specify it at the second parameter). The DoSomethingElse method works fine when called alone, but as soon at it's invoked by the event, the UI freezes since TaskFactory.Current will reuse the synchronization context task scheduler from my library continuation.
Finding out this could take some time, especially if the second task call is buried down in some complex call stack. Of course, the fix here is simple once you know how everything works: always specify TaskScheduler.Default for any operation you're expecting to be running on the thread pool. However, maybe the second task is started by another external library, not knowing about this behavior and naively using StartNew without a specific scheduler. I'm expecting this case to be quite common.
After wrapping my head around it, I can't understand the choice of the team writing the TPL to use TaskScheduler.Current instead of TaskScheduler.Default as the default:
It's not obvious at all, Default is not the default! And the documentation is seriously lacking.
The real task scheduler used by Current depends of the call stack! It's hard to maintain invariants with this behavior.
It's cumbersome to specify the task scheduler with StartNew since you have to specify the task creation options and cancellation token first, leading to long, less readable lines. This can be alleviated by writing an extension method or creating a TaskFactory that uses Default.
Capturing the call stack has additional performance costs.
When I really want a task to be dependent on another parent running task, I prefer to specify it explicitly to ease code reading rather than rely on call stack magic.
I know this question may sound quite subjective, but I can't find a good objective argument as to why this behavior is as it. I'm sure I'm missing something here: that's why I'm turning to you.
I think the current behavior makes sense. If I create my own task scheduler, and start some task that starts other tasks, I probably want all the tasks to use the scheduler I created.
I agree that it's odd that sometimes starting a task from the UI thread uses the default scheduler and sometimes not. But I don't know how would I make this better if I was designing it.
Regarding your specific problems:
I think the easiest way to start a new task on a specified scheduler is new Task(lambda).Start(scheduler). This has the disadvantage that you have to specify type argument if the task returns something. TaskFactory.Create can infer the type for you.
You can use Dispatcher.Invoke() instead of using TaskScheduler.FromCurrentSynchronizationContext().
[EDIT]
The following only addresses the problem with the scheduler used by Task.Factory.StartNew.
However, Task.ContinueWith has a hardcoded TaskScheduler.Current.
[/EDIT]
First, there is an easy solution available - see the bottom of this post.
The reason behind this problem is simple: There is not only a default task scheduler (TaskScheduler.Default) but also a default task scheduler for a TaskFactory (TaskFactory.Scheduler).
This default scheduler can be specified in the constructor of the TaskFactory when it's created.
However, the TaskFactory behind Task.Factory is created as follows:
s_factory = new TaskFactory();
As you can see, no TaskScheduler is specified; null is used for the default constructor - better would be TaskScheduler.Default (the documentation states that "Current" is used which has the same consequences).
This again leads to the implementation of TaskFactory.DefaultScheduler (a private member):
private TaskScheduler DefaultScheduler
{
get
{
if (m_defaultScheduler == null) return TaskScheduler.Current;
else return m_defaultScheduler;
}
}
Here you should see be able to recognize the reason for this behaviour: As Task.Factory has no default task scheduler, the current one will be used.
So why don't we run into NullReferenceExceptions then, when no Task is currently executing (i.e. we have no current TaskScheduler)?
The reason is simple:
public static TaskScheduler Current
{
get
{
Task internalCurrent = Task.InternalCurrent;
if (internalCurrent != null)
{
return internalCurrent.ExecutingTaskScheduler;
}
return Default;
}
}
TaskScheduler.Current defaults to TaskScheduler.Default.
I would call this a very unfortunate implementation.
However, there is an easy fix available: We can simply set the default TaskScheduler of Task.Factory to TaskScheduler.Default
TaskFactory factory = Task.Factory;
factory.GetType().InvokeMember("m_defaultScheduler", BindingFlags.SetField | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly, null, factory, new object[] { TaskScheduler.Default });
I hope I could help with my response although it's quite late :-)
Instead of Task.Factory.StartNew()
consider using: Task.Run()
This will always execute on a thread pool thread. I just had the same problem described in the question and I think that is a good way of handling this.
See this blog entry:
http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
It's not obvious at all, Default is not the default! And the documentation is seriously lacking.
Default is the default, but it's not always the Current.
As others have already answered, if you want a task to run on the thread pool, you need to explicitly set the Current scheduler by passing the Default scheduler into either the TaskFactory or the StartNew method.
Since your question involved a library though, I think the answer is that you should not do anything that will change the Current scheduler that's seen by code outside your library. That means that you should not use TaskScheduler.FromCurrentSynchronizationContext() when you raise the SomeOperationCompleted event. Instead, do something like this:
public void DoSomeOperationAsync() {
var context = SynchronizationContext.Current;
Task.Factory
.StartNew(() => Thread.Sleep(1000) /* simulate a long operation */)
.ContinueWith(t => {
context.Post(_ => OnSomeOperationCompleted(), null);
});
}
I don't even think you need to explicitly start your task on the Default scheduler - let the caller determine the Current scheduler if they want to.
I've just spent hours trying to debug a weird issue where my task was scheduled on the UI thread, even though I didn't specify it to. It turned out the problem was exactly what your sample code demonstrated: A task continuation was scheduled on the UI thread, and somewhere in that continuation, a new task was started which then got scheduled on the UI thread, because the currently executing task had a specific TaskScheduler set.
Luckily, it's all code I own, so I can fix it by making sure my code specify TaskScheduler.Default when starting new tasks, but if you aren't so lucky, my suggestion would be to use Dispatcher.BeginInvoke instead of using the UI scheduler.
So, instead of:
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
var task = Task.Factory.StartNew(() => Thread.Sleep(5000));
task.ContinueWith((t) => UpdateUI(), uiScheduler);
Try:
var uiDispatcher = Dispatcher.CurrentDispatcher;
var task = Task.Factory.StartNew(() => Thread.Sleep(5000));
task.ContinueWith((t) => uiDispatcher.BeginInvoke(new Action(() => UpdateUI())));
It's a bit less readable though.

Categories

Resources