【问题标题】:Two ways of calling asynchronous method. C#调用异步方法的两种方式。 C#
【发布时间】:2020-05-19 20:12:59
【问题描述】:

这两种方法有区别吗?或者程序在这两种情况下运行类似?如果有差异,您能说出这些差异是什么。

第一种方法:

Task myTask = MyFunctionAsync();
await myTask;

第二种方法:

await MyFunctionAsync();

【问题讨论】:

  • 如果出现异常,您可能会使用第一种方法获得更有用的堆栈跟踪。

标签: c# async-await task


【解决方案1】:

简短版:“不是真的,至少不是以有趣的方式”

长版:可等待对象不限于Task/Task<T>,因此可能(实际上是这样)创建可以正常编译的代码:

await MyFunctionAsync();

但不编译:

Task myTask = MyFunctionAsync();
await myTask;

仅仅是因为MyFunctionAsync() 返回的东西不是任务ValueTask<int> 就足够了,但如果你愿意,你可以制作异国情调的等待。但是:如果我们将Task 替换为var,即

var myTask = MyFunctionAsync();
await myTask;

然后现在唯一的区别是我们可以在代码中的其他点引用myTask,如果我们愿意的话。这并不少见。两个主要场景是

  • 结合对并发代码的多个检查,可能使用WhenAnyWhenAll
  • (通常在ValueTask[<T>]的情况下)检查等待的是否同步完成,以避免同步情况下的状态机开销

【讨论】:

    【解决方案2】:

    它们实际上是相同的。不同之处在于,第一种方法允许您在等待响应之前执行更多步骤。所以你可以用第一种方式同时启动多个任务,然后用await Task.WhenAll(myListOfTasks)等待它们。

    例如:

    var myTasks = myEmployees.Select(e => ProcessPayrollAsync(e));
    await Task.WhenAll(myTasks);
    

    如果你需要并发,我会使用第一种方法,如果它是一个简单的情况,我会使用第二种方法,因为它更短。

    【讨论】:

      【解决方案3】:

      在这种特殊情况下,两种形式的代码以类似的方式执行。 Homewer,考虑一下:

      public async Task<int> CalculateResult(InputData data) {
          // This queues up the work on the threadpool.
          var expensiveResultTask = Task.Run(() => DoExpensiveCalculation(data));
      
          // Note that at this point, you can do some other work concurrently,
          // as CalculateResult() is still executing!
      
          // Execution of CalculateResult is yielded here!
          var result = await expensiveResultTask;
      
          return result;
      }
      

      正如上面代码中的 cmets 所指出的,在任务正在运行和await 调用之间,您可以执行任何其他并发代码。

      欲了解更多信息,请阅读this 文章。

      【讨论】:

        【解决方案4】:

        真正帮助我理解 async-await 的是 Eric Lippert 在this interview 中描述的厨师类比。在中间某处搜索 async-await。

        在这里,他描述了一位正在做早餐的厨师。一旦他把水壶烧开来泡茶,他就不会闲着等水煮好了。相反,他把面包放在烤面包机里,茶放在茶壶里,然后开始切西红柿:每当他必须等待另一台机器或其他厨师完成他的工作时,他不会无所事事,而是开始下一个任务,直到他需要之前任务之一的结果。

        异步等待也是如此:每当另一个进程必须做某事时,您的线程除了等待另一个进程完成之外什么都不做,该线程可以环顾四周,看看它是否可以做其他事情。当涉及另一个冗长的过程时,您通常会看到 async-await:将数据写入文件、从数据库或 Internet 查询数据。每当您的线程必须执行此操作时,该线程可以命令其他进程执行某些操作,同时继续执行其他操作:

        Task<string> taskReadFile = ReadMyFileAsync(...);
        
        // not awaiting, we can do other things:
        DoSomethingElse();
        
        // now we need the result of the file:
        string fetchedData = await taskReadFile;
        

        那么会发生什么。 ReadMyFileAsync 是异步的,因此您知道在其内部某处,该方法正在等待。事实上,如果您忘记等待,编译器会发出警告。

        一旦你的线程看到等待,它就知道需要等待的结果,所以它不能继续。相反,它会向上调用堆栈继续处理(在我的示例中为 DoSomethingElse()),直到它看到等待。它再次向上调用堆栈并继续处理,等等。

        因此,实际上您的第一种方法和第二种方法之间没有真正的区别。您可以将其与以下内容进行比较:

        double x = Math.Sin(4.0)
        

        double a = 4.0;
        double x = Math.Sin(a);
        

        官方唯一的区别是在这些语句之后你仍然可以使用 a.同样,您可以在等待之后使用来自任务的信息:

        Task<MyData> myTask = FetchMyDataAsync(...);
        MyData result = await myTask;
        
        // if desired you can investigate myTask
        if (result == null)
        {
            // why is it null, did someone cancel my task?
            if (Task.IsCanceled)
            {
                Yell("Hey, who did cancel my task!");
            }
        }
        

        但大多数时候你对任务不感兴趣。如果您在执行任务时没有其他事情可做,我会等待它:

        MyData fetchedData = await FetchMyDataAsync(...)
        

        【讨论】:

        • 关于第一种方法。如果线程在方法中看到 await ,它会离开此方法并继续向下堆栈。我已经运行了代码,发现即使我不调用此任务的等待,我也可以看到它们的结果。如果我不等待它们所在的方法,为什么在执行中等待操作并返回结果? (我正在使用 UI 线程)我认为只有当我等待此方法返回的任务时,该线程才离开该方法并继续执行
        • 即使没有等待任务也可以完成。您可以定期检查属性IsCompleted,而不是等待,一旦返回true,您可以获得Task.Result。然而,这将是一个相当繁忙的等待。发明 async-await 的主要原因,是为了给调用者一个简单的方法来继续处理,而不是无所事事地等待另一个进程完成
        猜你喜欢
        • 2023-04-07
        • 1970-01-01
        • 2015-07-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-05-26
        • 1970-01-01
        相关资源
        最近更新 更多