为什么异步等待不按预期工作

时间:2015-03-05 11:55:14

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

我正在从教程中学习TPL(async / await),我试图使用控制台应用程序自己测试它。请不要被我的无知所冒犯。 我确信我在某处做错了 - 我写了下面的代码:

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

&#34;等待开始&#34>之间是否存在相当大的时间差距?和&#34;等待结束&#34;?

更新

在答案之后,我发现Task.Delay(..)和Thread.Sleep(..)并不相同,因为它们的工作方式完全不同。 这是一个很好的link,用一个例子来解释。

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

3 个答案:

答案 0 :(得分:5)

不,因为事实上你在打印Waiting started之前已经睡了7秒钟。这是发生的事情:

  • 主要调用ProcessNumber
  • ProcessNumber睡眠1秒
  • ProcessNumber调用IncrementNumber
  • IncrementNumber睡眠时间为6秒
  • IncrementNumber执行计算,然后返回已完成的任务
  • ProcessNumber等待完成的任务,然后继续
  • ProcessNumber返回已完成的任务
  • 主要打印“等待开始”
  • Main等待已完成的任务完成(no-op)
  • 主要打印“等待结束”

如果您将每个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(因为它等待的东西还没有完成),它会同步执行。这不像调用异步方法分成不同的线程。

答案 1 :(得分:3)

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

实际上你应该收到IncrementNumber号码的编译器警告这个异步方法缺少&#39; await&#39;运营商并将同步运行......等等。它基本上说你的方法将同步执行。 This answer explains it in detail

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

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

答案 2 :(得分:1)

您需要重构代码。像这样

    static async Task<int> IncrementNumber(int j_)
{
    await Task.Delay(...);
    return (j_ + 23) / 23;
}
  1. 具有关键字async的函数必须在正文中至少有一个等待 为了运行函数async。
  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;