I've read this question from Noseratio which shows a behaviour where TaskScheduler.Current
is not the same after an awaitable has finished its operation.
The answer states that :
If there is no actual task being executed, then
TaskScheduler.Current
is the same asTaskScheduler.Default
Which is true . I already saw it here :
TaskScheduler.Default
- Returns an instance of the
ThreadPoolTaskScheduler
TaskScheduler.Current
- If called from within an executing task will return the
TaskScheduler
of the currently executing task- If called from any other place will return
TaskScheduler.Default
But then I thought , If so , Let's do create an actual Task
(and not just Task.Yield()
) and test it :
async void button1_Click_1(object sender, EventArgs e)
{
var ts = TaskScheduler.FromCurrentSynchronizationContext();
await Task.Factory.StartNew(async () =>
{
MessageBox.Show((TaskScheduler.Current == ts).ToString()); //True
await new WebClient().DownloadStringTaskAsync("http://www.google.com");
MessageBox.Show((TaskScheduler.Current == ts).ToString());//False
}, CancellationToken.None, TaskCreationOptions.None,ts).Unwrap();
}
First Messagebox is "True" , second is "False"
Question:
As you can see , I did created an actual task.
I can understand why the first MessageBox yield True
. Thats becuase of the :
If called from within an executing task will return the TaskScheduler of the currently executing task
And that task does have ts
which is the sent TaskScheduler.FromCurrentSynchronizationContext()
But why the context is not preserved at the second MessageBox ? To me , It wasn't clear from Stephan's answer.
Additional information :
If I write instead (of the second messagebox ) :
MessageBox.Show((TaskScheduler.Current == TaskScheduler.Default).ToString());
It does yield true
. But why ?
A good way to think about this is to imagine that asynchronous methods have “pause” and “play” buttons. When the executing thread reaches an await expression, it hits the “pause” button and the method execution is suspended.
Yes. If you don't need to wait, don't wait.
The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete. await can only be used inside an async method.
The reasons for confusion are these:
TaskScheduler
. The default case for code running on the UI thread is that TaskScheduler.Current
stores the ThreadPoolTaskScheduler
and SynchronizationContext.Current
stores WindowsFormsSynchronizationContext
(or the relevant one in other UI apps)ThreadPoolTaskScheduler
in TaskScheduler.Current
doesn't necessarily mean it's the TaskScheduler
being used to run the current piece of code. It also means TaskSchdeuler.Current == TaskScheduler.Default
and so "there is no TaskScheduler
being used". TaskScheduler.FromCurrentSynchronizationContext()
doesn't return an "acutal" TaskScheduler
. It returns a "proxy" that posts tasks straight to the captured SynchronizationContext
.So if you run your test before starting the task (or in any other place) you'll get the same result as after the await:
MessageBox.Show(TaskScheduler.Current == TaskScheduler.FromCurrentSynchronizationContext()); // False
Because TaskScheduler.Current
is ThreadPoolTaskScheduler
and TaskScheduler.FromCurrentSynchronizationContext()
returns a SynchronizationContextTaskScheduler
.
This is the flow of your example:
SynchronizationContextTaskScheduler
from the UI's SynchronizationContext
(i.e. WindowsFormsSynchronizationContext
).Task.Factory.StartNew
on that TaskScheduler
. Since it's just a "proxy" it posts the delegate to the WindowsFormsSynchronizationContext
which invokes it on the UI thread.SynchronizationContextTaskScheduler
.WindowsFormsSynchronizationContext
.WindowsFormsSynchronizationContext
and not the SynchronizationContextTaskScheduler
since SynchronizationContext
s have precedence (this can be seen in Task.SetContinuationForAwait
). It then runs regularly on the UI thread, without any "special" TaskScheduler
so TaskScheduler.Current == TaskScheduler.Default
.So, the created task runs on the proxy TaskScheduler
which uses the SynchronizationContext
but the continuation after the await is posted to that SynchronizationContext
and not the TaskScheduler
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With