更新
经过一番思考,我得出结论,我可能没有问过正确的问题。
给出以下代码:
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
。 - 但是,我不确定它是如何决定是否允许它。
答案 0 :(得分:4)
如果您尝试在请求生命周期中不允许的位置(或完全在请求上下文之外)执行异步操作,则ASP.NET会抛出此特定异常。
你不应该在ASP.NET MVC中看到这一点。有两件事需要检查:
UseTaskFriendlySynchronizationContext
set to true
or have httpRuntime.targetFramework
set to 4.5
。更新:经过反思,在ASP.NET MVC中还有另外两个可能导致此问题的情况:
答案 1 :(得分:1)
您找不到将UseTaskFriendlySynchronizationContext设置为true的代码,因为设置它的代码不是.NET框架的一部分。
在编译期间,c#5.0编译器会生成操作同步上下文的代码。 C#编译器创建一个状态机结构,其名称为<yourmethod>d__0
,以避免与代码冲突。这个结构中有一个叫做“MoveNext”的方法。这是捕获同步上下文的地方。大多数异步代码也会被编译器移动到那里。