等待异步方法在结果需要之前不返回

我无法解决我遇到异步/等待的问题。

简而言之,我有一个控制器,装饰有属性。 此属性从i / o密集型进程(filesystem / db / api等…)获取指定的内容

然后,它将返回的内容设置为ViewBag上的Dictionary

然后,在视图中,我可以做这样的事情,以检索内容:

 @(ViewBag.SystemContent["Common/Footer"]) 

我遇到的问题是,它第一次运行时,内容没有返回,并且通过字符串索引检索值的调用失败,因为它不存在。
点击F5,没关系。

控制器动作非常简单:

 [ProvideContent("Common/Footer")] public class ContactDetailsController : Controller { public async Task Index() { //omitted for brevity - awaits some other async methods return View(); } } 

属性

 public override async void OnActionExecuting(ActionExecutingContext filterContext) { if (filterContext.Result is ViewResult) { var localPath = filterContext.RouteData.Values["controller"] + "/" + filterContext.RouteData.Values["action"]; if (!_useControllerActionAsPath) localPath = _path; var viewResult = filterContext.Result as ViewResult; //this seems to go off and come back AFTER the view requests it from the dictionary var content = await _contentManager.GetContent(localPath); if (viewResult.ViewBag.SystemContent == null) viewResult.ViewBag.SystemContent = new Dictionary(); viewResult.ViewBag.SystemContent[localPath] = new DisplayContentItem(content, localPath); } 

编辑

更改属性中的以下行:

 var content = await _contentManager.GetContent(localPath); 

 var content = Task.Factory.StartNew(() => manager.GetContent(localPath).Result, TaskCreationOptions.LongRunning).Result; 

解决了这个问题,但我觉得这与我在Stephen Clearys博客上读到的所有内容相违背……

我不是100%熟悉ASP.Net MVC堆栈以及所有这些是如何工作的,但我会对它进行一次尝试。

OnActionExecuting()文档说:

在调用action方法之前调用。

由于您覆盖了以前的同步方法并使其异步,因此期望代码完整,并为下一个执行步骤做好准备。

理论执行路径:

 public void ExecuteAction() { OnActionExecuting(); OnActionExecution(); OnActionExecuted(); } 

由于你覆盖了OnActionExecuting()方法,执行堆栈基本上仍然是相同的,但是下一个要执行的代码( ExecuteAction()OnActionExecuted()以及任何名为ExecuteAction() )都期望进行同步调用,所以据他们所知,一切都很好,并准备继续运行。

基本上,这归结为OnActionExecuting()不是一个异步方法,没有任何期望它。 (它在MVC 6中也不是异步的。)

OnActionExecuting()和它的顺序调用之后,同步调用的东西是引用viewResult.ViewBag.SystemContent ,因此它没有获得你想要的值。 正如你在标题中所说的那样,

等待异步方法在结果需要之前不返回。

使用任务的“捕获”是您不能保证任务何时完成,但保证它将完成。

潜在解决方案

  • GetContent()调用移出该事件。
  • 存储为GetContent()创建的任务,找到使用viewResult.ViewBag.SystemContent的下一个位置,并检查任务完成情况或等待完成。
  • 为GetContent()方法添加超时间隔。 (执行此操作的方式有很多种.MSDN Docs for Task类这不会解决您的问题。

编辑:用于在控制器中存储任务的代码示例

 [ProvideContent("Common/Footer")] public class ContactDetailsController : Controller { /* * BEGINNING OF REQUIRED CODE BLOCK */ private Task _getContentForLocalPathTask; private string _localPath; /* * END OF REQUIRED CODE BLOCK */ public async Task Index() { //omitted for brevity - awaits some other async methods /* * BEGINNING OF REQUIRED CODE BLOCK */ string content = await _getContentForLocalPath; viewResult.ViewBag.SystemContent[_localPath] = new DisplayContentItem(content, _localPath); /* * END OF REQUIRED CODE BLOCK */ return View(); } public override async void OnActionExecuting(ActionExecutingContext filterContext) { if (filterContext.Result is ViewResult) { var localPath = filterContext.RouteData.Values["controller"] + "/" + filterContext.RouteData.Values["action"]; if (!_useControllerActionAsPath) localPath = _path; var viewResult = filterContext.Result as ViewResult; /* * BEGINNING OF REQUIRED CODE BLOCK */ _localPath = localPath; _getContentForLocalPathTask = _contentManager.GetContent(localPath); /* * END OF REQUIRED CODE BLOCK */ if (viewResult.ViewBag.SystemContent == null) viewResult.ViewBag.SystemContent = new Dictionary(); } }