I would like to know the reasoning behind the way the compiler choose the TaskScheduler when compiling using the async keyword.
My test method is called by SignalR (
When you call:
private async void SendUpdates()
With the call to Task.Run and using the async keyword on the anonymous delegate, you aren't actually providing a continuation; you start the Task, and you're giving the Run
method a continuation, which it then processes. That continuation is not channeled back in any meaningful to the code that called Task.Run
.
This is why you get the exception, the handler doesn't know to await on the Task
that the call to Task.Run
produces.
That said:
private void SendUpdates()
Works because the task is created and the code doesn't capture a SynchronizationContext (because there is no async
keyword on the method, Task
instances don't capture it by default). You are firing the task, but it's fire-and-forget.
And the following works too:
private async Task SendUpdates()
Namely because in returning the Task
, you've returned an awaitable that the callback can work with.
To answer your question directly, the compiler will make sure to get the SynchronizationContext
returned from SynchronizationContext.Current before you call await
; whatever continuation is called after the awaitable returns will be called using that SynchronizationContext
.
One of the primary guidelines in writing async
code is "avoid async void
" - that is, use async Task
instead of async void
unless you're implementing an async
event handler.
async void
methods use SynchronizationContext
's OperationStarted
and OperationCompleted
; see my MSDN article It's All about the SynchronizationContext for more details.
ASP.NET detects the call to OperationStarted
and (correctly) rejects it because it's illegal to put an async
event handler there. When you correct the code to use async Task
, then ASP.NET no longer sees an async
event handler.
You may find my intro to async / await post helpful.