【问题标题】:Why the async await not working as expected为什么异步等待没有按预期工作
【发布时间】:2015-05-06 18:57:28
【问题描述】:

我正在从教程中学习 TPL(异步/等待),并尝试使用控制台应用程序自己对其进行测试。请不要因为我的无知而生气。 我确定我在某处做错了-我编写了以下代码:

static void Main(string[] args_)
{
    Task<int> task = ProcessNumber(25);
    Console.WriteLine(string.Format("{0}: Waiting started...", DateTime.Now));
    task.Wait();
    Console.WriteLine(string.Format("{0}: Waiting ended...", DateTime.Now));
    Console.WriteLine(task.Result);

    Console.WriteLine("Press any key to terminate!");
    Console.ReadLine();
}

static async Task<int> ProcessNumber(int i_)
{
    Thread.Sleep(1000);
    var integer = await IncrementNumber(i_ * 23);
    return integer;
}

static async Task<int> IncrementNumber(int j_)
{
    Thread.Sleep(6000);
    return (j_ + 23) / 23;
}

这是纯 C# 控制台代码。 我的问题是为什么我会得到以下输出:

3/5/2015 5:22:37 PM: Waiting started...
3/5/2015 5:22:37 PM: Waiting ended...
26

“等待开始”和“等待结束”之间不应该有相当大的时间间隔吗?

更新

得到答案后,我发现 Task.Delay(..) 和 Thread.Sleep(..) 不一样,因为它们的工作方式非常不同。 这是一个很好的link,用一个例子来解释。

似乎将 TPL 视为另一个多线程框架是错误的。所有的答案都对我有帮助,所以我投了所有的票。但是,我选择 Jon 的答案,因为它最具说明性,也是第一个出现的答案。 谢谢大家!

【问题讨论】:

  • 始终注意编译器警告。对于这段代码,编译器会给你一个警告,告诉你到底出了什么问题。

标签: c# task-parallel-library async-await .net-4.5 c#-5.0


【解决方案1】:

这是因为您的方法都不是异步的。它们通过返回Task 假装是异步的,但它们都是同步执行的。

事实上,您应该收到IncrementNumber 编号的编译器警告,说此异步方法缺少“等待”运算符,将同步运行......等等。它基本上说您的方法将同步执行。 This answer explains it in detail.

所以当ProcessNumber 返回时,整个嵌套方法调用已经同步完成。

将方法更改为使用await Task.Delay 而不是Thread.Sleep 以使其异步。

【讨论】:

    【解决方案2】:

    不,因为实际上您在打印 Waiting started 之前已经睡了 7 秒。以下是发生的事情:

    • 主要调用 ProcessNumber
    • ProcessNumber 休眠 1 秒
    • ProcessNumber 调用 IncrementNumber
    • IncrementNumber 休眠 6 秒
    • IncrementNumber 执行计算,然后返回已完成的任务
    • ProcessNumber 等待完成的任务,然后继续
    • ProcessNumber 返回一个已完成的任务
    • 主要打印“等待开始”
    • Main 等待已完成的任务完成(无操作)
    • 主要打印“等待结束”

    如果您将每个Thread.Sleep 更改为

    await Task.Delay(...);
    

    然后你会看到你所期望的。新流程将是:

    • 主要调用 ProcessNumber
    • ProcessNumber 调用 Task.Delay 并等待结果...
    • ...由于尚未完成,ProcessNumber 将一个正在进行的任务返回给 Main
    • 主要打印“等待开始”
    • Main 等待 ProcessNumber 返回的任务完成
    • Task.Delay 任务完成
    • ProcessNumber 中的延续开始执行...
    • ProcessNumber 调用 IncrementNumber
    • IncrementNumber 调用 Task.Delay 并等待结果...
    • ...由于尚未完成,IncrementNumber 将一个正在进行的任务返回给 ProcessNumber
    • ProcessNumber 等待未完成的任务,因此继续退出。
    • 第二个延迟任务完成
    • IncrementNumber 中的延续执行
    • 到达 IncrementNumber 的末尾,并设置关联任务的结果
    • ProcessNumber 中的延续(正在等待该任务)现在执行
    • 到达 ProcessNumber 的末尾,并设置关联任务的结果
    • 在 Main 中对 Task.Wait 的调用完成
    • 主要打印“等待结束”等

    理解这一点非常重要,直到异步方法到达它实际需要暂停的第一个await(因为它正在等待的事情还没有完成),它才会同步执行。这不像调用异步方法会拆分到不同的线程。

    【讨论】:

    • 感谢 Jon 的回答 - await Task.Delay(..) 解决方案完美运行。除此之外,我更改了 Thread.Sleep 代码: static async Task SleepForMilSec(int milSec_){Thread.Sleep(milSec_);现在,我使用 await SleepForMilSec(..),而不是 ProcessNumber 和 IncrementNumber 方法中的直接 Thread.Sleep 调用。仍然只有 6 秒的延迟。这意味着:在 ProcessNumber 方法中的第一次等待调用被阻塞。任何想法?编辑:顺便说一下,你在 Pluralsight 中的异步等待课程是我要学习的下一个教程。 :)
    • @James:你的SleepForMilSec 方法应该有一个警告,说你有一个没有等待的异步方法,因此它将同步执行。请注意:)
    • 是的..我注意到警告并且我也更新了问题!谢谢乔恩。这很有帮助!
    【解决方案3】:

    你需要重构你的代码。像这样

        static async Task<int> IncrementNumber(int j_)
    {
        await Task.Delay(...);
        return (j_ + 23) / 23;
    }
    
    1. 带有关键字 async 的函数在主体内必须至少有一个 await 为了运行异步函数。
    2. Thread.Sleep(...) 也对此负责。
    3. 如果您发现 CPU 绑定任务而不是 Thread.Sleep,那么您需要执行类似的操作。

      //it will create a new thread then sleep.
      await Task.Run( () => (  CPUBoundTask());
      var integer = await IncrementNumber(i_ * 23);
      return integer;
      

    【讨论】:

      猜你喜欢
      • 2018-05-05
      • 2019-04-19
      • 2021-03-27
      • 2020-03-04
      • 2019-04-07
      • 2023-03-03
      • 2022-01-06
      • 2022-01-22
      • 2018-12-02
      相关资源
      最近更新 更多