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

时间:2015-04-08 15:42:16

标签: c# async-await

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

简而言之,我有一个控制器,用属性装饰。 此属性从i / o密集型进程(文件系统/ db / api等...)

获取指定的内容

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

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

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

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

控制器动作非常简单:

[ProvideContent("Common/Footer")]
public class ContactDetailsController : Controller
{
    public async Task<ActionResult> 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<string, MvcHtmlString>();

        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博客上读到的所有内容相违背...

1 个答案:

答案 0 :(得分:8)

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

OnActionExecuting()文档说:

  

在调用action方法之前调用。

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

理论执行路径:

public void ExecuteAction()
{
 OnActionExecuting();

 OnActionExecution();

 OnActionExecuted();
}

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

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

OnActionExecuting()及其连续通话后同步调用的某些内容正在引用viewResult.ViewBag.SystemContent,因此它无法获得您的值{&#39}。想要的。正如你在标题中所说的那样,

  

等待异步方法在结果出现之前不返回。

&#39;捕获&#39;使用“任务”是指您无法保证 任务完成,但保证 完成。

潜在的解决方案:

  • GetContent()来电移出该事件。
  • 存储为GetContent()创建的任务,找到您viewResult.ViewBag.SystemContent使用的下一个位置,并检查任务是否完成或等待完成。
  • 为GetContent()方法添加超时间隔。 (有很多种方法可以做到这一点。MSDN Docs for Task class 这不会解决你的问题。

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

[ProvideContent("Common/Footer")]
public class ContactDetailsController : Controller
{

/*
 * BEGINNING OF REQUIRED CODE BLOCK
 */
    private Task<string> _getContentForLocalPathTask; 
    private string _localPath;

/*
 * END OF REQUIRED CODE BLOCK
 */
    public async Task<ActionResult> 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<string, MvcHtmlString>();

    }
}