async关键字和TaskScheduler的选择

我想知道编译器在使用async关键字进行编译时选择TaskScheduler的方式背后的原因。

我的测试方法由OnConnectedAsync方法上的SignalR(ASP.NET主机,IIS8,websocket传输)调用。

protected override async Task OnConnectedAsync(IRequest request, string connectionId) { SendUpdates(); } 

在Current同步上下文中启动任务将导致System.Web.AspNetSynchronizationContext.OperationStarted()中的InvalidOperationException

此时无法启动异步操作。 异步操作只能在异步处理程序或模块中启动,或者在页面生命周期中的某些事件中启动。 如果在执行页面时发生此exception,请确保将页面标记为

精细。 使用此SendUpdates定义,我得到以上exception:

  private async void SendUpdates() { Task.Run(async () => { while (true) { await Task.Delay(1000); await Connection.Broadcast("blabla"); } }); } 

但更有趣的是,当我没有得到例外。 以下作品:

  private void SendUpdates() 

以下也有效

  private async Task SendUpdates() 

这最后一个也有效,但它与上面的例子基本相同。

  private Task SendUpdates() { return Task.Run(async () => { while (true) { await Task.Delay(1000); await Connection.Broadcast("blabla"); } }); } 

你知道编译器如何选择在这里使用哪个调度程序吗?

编写async代码的主要原则之一是“避免async void ” – 也就是说,除非您正在实现async事件处理程序,否则请使用async Task而不是async void

async void方法使用SynchronizationContextOperationStartedOperationCompleted ; 请参阅我的MSDN文章。有关详细信息,请参阅SynchronizationContext 。

ASP.NET检测到对OperationStarted的调用并且(正确地)拒绝它,因为在那里放置async事件处理程序是非法的。 当您更正代码以使用async Task ,ASP.NET不再看到async事件处理程序。

你可能会发现我的async / await post介绍很有帮助。

你打电话的时候:

 private async void SendUpdates() 

通过调用Task.Run并在匿名委托上使用async关键字 ,您实际上并没有提供延续; 你启动Task ,然后你给Run方法一个延续,然后它处理。 对于调用Task.Run的代码,这种延续不会被带回任何有意义的Task.Run

这就是为什么你得到exception,处理程序不知道TaskawaitTask.Run的调用产生。

那说:

 private void SendUpdates() 

之所以有效,是因为创建了任务并且代码没有捕获SynchronizationContext (因为方法上没有async关键字, Task实例默认不捕获它)。 你正在解雇这个任务,但这是一场不容易的事。

以下也是如此:

 private async Task SendUpdates() 

也就是因为在返回Task ,你已经返回了一个等待回调可以使用的等待。

要直接回答您的问题,编译器将确保在调用await之前从SynchronizationContext.Current返回SynchronizationContext.Current ; 在使用SynchronizationContext调用等待返回之后调用的任何延续。