8

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

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

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

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

此时无法启动异步操作。异步操作只能在异步处理程序或模块内或在页面生命周期中的某些事件期间启动。如果在执行 Page 时发生此异常,请确保将 Page 标记为<%@ 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");
                }
            });

    }

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

4

2 回答 2

12

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

async void方法使用SynchronizationContext'sOperationStartedOperationCompleted; 有关更多详细信息,请参阅我的 MSDN 文章It's All about the SynchronizationContext

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

您可能会发现我的介绍async/await帖子很有帮助。

于 2012-10-24T05:35:52.610 回答
3

你打电话时:

private async void SendUpdates()

通过调用Task.Run和使用匿名委托上的async关键字,您实际上并没有提供延续;您启动Task,并且您正在为该Run方法提供一个延续,然后它会对其进行处理。该延续不会以任何对调用的代码有意义的方式返回Task.Run

这就是为什么你得到异常,处理程序不知道调用产生await的原因。TaskTask.Run

那说:

private void SendUpdates()

工作是因为创建了任务并且代码没有捕获 a SynchronizationContext(因为方法上没有async关键字,Task实例默认情况下不会捕获它)。您正在执行任务,但它是一劳永逸的。

以下也有效:

private async Task SendUpdates()

即因为在返回 时Task,您返回了一个回调可以使用的等待对象。

要直接回答您的问题,编译器将确保在您调用之前获取SynchronizationContext返回值;在可等待返回之后调用的任何延续都将使用 that 调用。SynchronizationContext.CurrentawaitSynchronizationContext

于 2012-10-23T13:40:47.427 回答