【问题标题】:Chaining async calls together - call never returns将异步调用链接在一起 - 调用永远不会返回
【发布时间】:2014-03-21 18:29:31
【问题描述】:

我正在进行异步调用的层次结构。看起来基本上是这样的:

async MyObject MainWorker()
{
    return await Task.Run(SomeSynchronousFunction);
}

async MyObject InbetweenFunction1()
{
    //do some things
    return await MainWorker();
}

async MyObject InbetweenFunction2()
{
    //do some things 
    return await MainWorker();
}

void ReturnObject TopFunction()
{
    Task<MyObject> return1 = InbetweenFunction1();
    Task<MyObject> return2 = InbetweenFunction2();

    while (!return1.IsComplete || !return2.IsComplete)
         Thread.Sleep(100);

    //access return1.Return and return2.Return values and return from this function
}

所以我有几个级别的异步调用。顶级方法进行两次异步调用,等待它们完成(通过轮询),然后访问它们的返回值并对这些值进行处理。问题是,没有一个异步调用完成。与 Task.Run 异步调用的函数 SomeSynchronousFunction 应该最多需要几秒钟,但我等了 30 多秒并且没有结果。

这是我第一次尝试使用新的 async/await 关键字。我是不是做错了什么?

【问题讨论】:

  • 什么是MyObject?该代码甚至不应该编译,它应该是async Task&lt;MyObject&gt; InbetweenFunction1()(除非 MyObject 是从Task 派生的某个类)。请编辑您的问题,使其成为SSCCE,否则如果我们一开始就无法理解您要做什么,将很难向您展示正确的方法。
  • 除非您绝对需要您的主线程在轮询循环中阻塞,否则您可能需要在 Task.WhenAll 上调用 await 并使用您的任务数组。
  • 检查stackoverflow.com/questions/14485115/… ...旁注:按顺序等待结果会容易得多...
  • 你没有指定这个的执行环境,我敢打赌它要么是WinForms,要么是WPF。 Stephen Cleary 详细解释了您所看到的僵局的性质:blog.stephencleary.com/2012/07/dont-block-on-async-code.html
  • @ScottChamberlain 即使MyObject 确实派生自Task,该代码仍然无法编译。

标签: c# .net async-await


【解决方案1】:

我做错了什么吗?

是的,您正在忙着等待(循环直到条件变为true),除非没有其他选择,否则您不应该这样做。这里有一个更好的选择:使用Wait(),或Task.WaitAll()

但是这样做很可能实际上不会使您的代码正常工作。真正的问题可能是the classic await deadlock:您处于同步上下文中(通常是 UI 线程或 ASP.NET 请求上下文)并且您的 awaits 尝试在该上下文中恢复,但您也阻止了它等待Tasks 完成的上下文。

解决这个问题的正确方法是在TopLevelFunction() 中也使用await。这将要求您也创建该函数async,这意味着它的调用者现在也必须是async,等等。这被称为“async all way”。

所有async 方法都应该是async Task 方法,但顶级事件处理程序除外,它必须是async void。 (但你不应该在其他任何地方使用async void 方法,因为它们不能是awaited。)

这意味着你的函数会变成:

async Task<ReturnObject> TopFunction()
{
    Task<MyObject> return1 = InbetweenFunction1();
    Task<MyObject> return2 = InbetweenFunction2();

    await Task.WhenAll(return1, return2);

    //access await return1 and await return2 values and return from this function
}

【讨论】:

    【解决方案2】:

    下面是一些可行的代码:

        private async Task<string> DoMainWork()
        {
            await Task.Delay(3000);
            return "MainWorker";
        }
    
        private async Task<string> InbetweenFunction1()
        {
            // do something
            await Task.Delay(1000);
            return await DoMainWork() + "1";
        }
    
        private async Task<string> InbetweenFunction2()
        {
            // do something
            await Task.Delay(2000);
            return await DoMainWork() + "2";
        }
    
        public string TopFunction()
        {
            string return1 = null;
            string return2 = null;
    
            var taskList = new List<Task>();
            taskList.Add(Task.Run(async () => return1 = await InbetweenFunction1()));
            taskList.Add(Task.Run(async () => return2 = await InbetweenFunction2()));
    
            Task.WaitAll(taskList.ToArray());
    
            return return1 + return2;
        }
    

    【讨论】:

      【解决方案3】:

      你想要的是一个额外的功能来组合以前的任务

      public async Task<string> Combine(Task<string> a, Task<string b){
          var aa = await a;
          var bb = await b;
          return aa + bb; 
      }
      

      public string TopFunction()
      {
          var task = Combine(InBetweenFunction2(), InVetweenFunction2());
      
          // Blocking call
          // Really you shouldn't be doing this unless you really really
          // have to.
          task.Wait();
      
          return task.Result;
      }
      

      注意really really 必须使用阻塞调用的地方之一是应用程序的入口点main function,它不能被声明为async,因此不能使用await

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-01-16
        • 1970-01-01
        • 2017-06-08
        • 1970-01-01
        • 2017-12-06
        • 2017-07-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多