何时设置AllowVoidAsyncOperations,何时调用异步ActionResults?

时间:2013-06-09 19:51:28

标签: c# asp.net-mvc multithreading asynchronous

更新

经过一番思考,我得出结论,我可能没有问过正确的问题。

给出以下代码:

public class HomeController : Controller
{
    public Task<string> DownloadAsync(string url)
    {
        using (var web = new WebClient())
        {
            return web.DownloadStringTaskAsync(url);
        }
    }

    // THROWS EXCEPTION
    public ActionResult Index()
    {
        var data = DownloadAsync("http://google.dk");
        return Content(data.Result);
    }

    // WORKS
    public async Task<ActionResult> IndexWorks()
    {
        var data = await DownloadAsync("http://google.dk");
        return Content(data);
    }
}

很明显(特别是在阅读@Stephen Cleary的博客文章后),ActionResult Index()代码将导致死锁。但为什么呢?

经过一番挖掘后,我发现.NET 4.5引入了一个新的AspNetSynchronizationContext,这应该更像是“任务友好”。下载.NET 4.5的源代码并查看新的AspNetSynchronizationContext,我认为调用OperationStarted将导致检查名为AllowVoidAsyncOperations的布尔值。如果值为true,则没问题。但是,如果此布尔值的值为false,则会抛出以下异常:

  

此时无法启动异步操作。异步   操作只能在异步处理程序中启动或   模块或在页面生命周期中的某些事件期间。如果这   执行页面时发生异常,确保页面为   标记为&lt;%@ Page Async = \“true \”%&gt;。

通过大量的反思,我认为,调用async Task<ActionResult> IndexWorks()会以某种方式将AllowVoidAsyncOperations设置为true。 - 在调用同步版本时,它保持默认值:false。

我的问题是:

异步ActionResult何时调用AspNetSynchrnoizationContext的内部方法,将AllowVoidAsyncOperations设置为true? - 到目前为止,我已将其缩小到CallHandlerExecutionStep班级内的班级HttpApplication。 - 但是,我不确定它是如何决定是否允许它。

2 个答案:

答案 0 :(得分:4)

如果您尝试在请求生命周期中不允许的位置(或完全在请求上下文之外)执行异步操作,则ASP.NET会抛出此特定异常。

你不应该在ASP.NET MVC中看到这一点。有两件事需要检查:

  1. 确保您在.NET 4.5上运行。我怀疑你已经是,否则你根本就不会看到这条消息。
  2. 确保您拥有UseTaskFriendlySynchronizationContext set to true or have httpRuntime.targetFramework set to 4.5
  3. 更新:经过反思,在ASP.NET MVC中还有另外两个可能导致此问题的情况:

    1. 确保您没有调用任何async void方法。
    2. 确保您没有使用EAP组件。例如:
      1. HttpClientTAP,因此可以使用。
      2. HttpWebRequestAPM,因此围绕它的TAP包装器可以正常工作。
      3. WebClientEAP,因此会导致此错误。无论您使用DownloadStringAsync还是DownloadStringTaskAsync
      4. ,都是如此

答案 1 :(得分:1)

您找不到将UseTaskFriendlySynchronizationContext设置为true的代码,因为设置它的代码不是.NET框架的一部分。

在编译期间,c#5.0编译器会生成操作同步上下文的代码。 C#编译器创建一个状态机结构,其名称为<yourmethod>d__0,以避免与代码冲突。这个结构中有一个叫做“MoveNext”的方法。这是捕获同步上下文的地方。大多数异步代码也会被编译器移动到那里。