async关键字和TaskScheduler的选择

时间:2012-10-23 13:08:03

标签: c# signalr async-await

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

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

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

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

  

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

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

    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");
                }
            });

    }

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

2 个答案:

答案 0 :(得分:12)

编写async代码的主要指导原则之一是“避免async void” - 即使用async Task代替async void,除非您正在实施async 1}}事件处理程序。

async void方法使用SynchronizationContext的{​​{1}}和OperationStarted;有关详细信息,请参阅我的MSDN文章It's All about the SynchronizationContext

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

您可能会发现我的intro to async / await post有帮助。

答案 1 :(得分:3)

致电时:

private async void SendUpdates()

通过调用Task.Run并使用匿名委托上的async keyword,您实际上并未提供延续;你开始Task,然后你给Run方法一个延续,然后它处理。对于调用Task.Run的代码,该延续不会被带回任何有意义的内容。

这就是为什么你得到异常的原因,处理程序不会<{>}知道Task Task.Run private void SendUpdates() async的调用产生的原因。

那说:

Task

正常运行,因为任务已创建且代码未捕获await(因为方法上没有private async Task SendUpdates() 关键字,Task实例默认不捕获它)。你正在解雇这个任务,但这是一次又一次的忘记。

以下也有效:

SynchronizationContext

即因为在返回await时,您已经返回了等待回调可以使用的内容。

要直接回答您的问题,编译器会确保在您致电SynchronizationContext之前从SynchronizationContext返回{{1}};在使用{{1}}

之后调用等待的回报之后调用任何延续