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

Ale*_*lex 5 c# async-await

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

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

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

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

@(ViewBag.SystemContent["Common/Footer"])
Run Code Online (Sandbox Code Playgroud)

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

控制器动作非常简单:

[ProvideContent("Common/Footer")]
public class ContactDetailsController : Controller
{
    public async Task<ActionResult> Index()
    {
        //omitted for brevity - awaits some other async methods
        return View();
    }
}
Run Code Online (Sandbox Code Playgroud)

属性

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);
    }
Run Code Online (Sandbox Code Playgroud)

编辑

更改我的属性中的以下行:

var content = await _contentManager.GetContent(localPath);
Run Code Online (Sandbox Code Playgroud)

var content = Task.Factory.StartNew(() =>
            manager.GetContent(localPath).Result, TaskCreationOptions.LongRunning).Result;
Run Code Online (Sandbox Code Playgroud)

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

Cam*_*ron 8

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

OnActionExecuting()文件说:

在调用action方法之前调用.

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

理论执行路径:

public void ExecuteAction()
{
 OnActionExecuting();

 OnActionExecution();

 OnActionExecuted();
}
Run Code Online (Sandbox Code Playgroud)

既然你推翻了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<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>();

    }
}
Run Code Online (Sandbox Code Playgroud)

  • +1.`async`的第一条规则是避免`async void`.在这种情况下,`OnActionExecuting`在命中它的`await`时返回到ASP.NET,导致请求管道继续.我建议将异步操作存储在`Task <T>`中(即,在你的控制器类型中)并在你的控制器动作中"等待"该任务. (3认同)