C#异步和等待不等待代码完成

4
为什么以下的 asyncawait 没有起作用?我正在尝试学习并理解我的代码哪里出了问题。
class Program
{
    static void Main(string[] args)
    {

        callCount();

    }

    static void count()
    {
        for (int i = 0; i < 5; i++)
        {
            System.Threading.Thread.Sleep(2000);
            Console.WriteLine("count loop: " + i);
        }
    }

    static async void callCount()
    {
        Task task = new Task(count);
        task.Start();
        for (int i = 0; i < 3; i++)
        {
            System.Threading.Thread.Sleep(4000);
            Console.WriteLine("Writing from callCount loop: " + i);
        }
        Console.WriteLine("just before await");
        await task;
        Console.WriteLine("callCount completed");
    }
}

该程序开始执行count()方法,但是在完成之前就退出了。使用await task语句时,我希望它等待所有循环(0、1、2、3、4)完成count()方法后再退出。但是我只得到了“count loop: 0”。但是它确实在经过所有的callCount()。好像await task没有起到任何作用。我希望count()和callCount()都异步运行,并在完成后返回主函数。

5
如果你不使用await等待callCount的结果,那么你认为会发生什么? - DavidG
发布的程序将执行所有循环,但仅因为callCount中的循环总时间比count中的循环长。在.Net控制台应用程序中,ProgramMain可以是私有的,没有问题。@Arturo Menchaca在下面给出了正确的答案。 - Ergwun
请看我对Arturo的回复。在stackoverflow上看到了类似的问题响应后,我尝试了这个方法,但没有成功。使用Task作为返回类型。为什么"await task"不能阻止从callCount退出,直到task(count())完成?我甚至尝试过这个: - jaykum
1
@jaykum:除了避免在Main中使用async void和阻塞主线程以防止应用程序退出之外,您还应该永远不要使用Task构造函数或Task.Start(请改用Task.Run)。 - Stephen Cleary
你应该只需要将 Task task = new Task(count); task.Start(); 替换为 Task task = Task.Run(() => count());,它应该与原始代码的行为相同。 - Stephen Cleary
显示剩余8条评论
1个回答

24

当你执行一个async方法时,它会同步运行直到遇到一个await语句,然后剩余的代码执行异步,并返回到调用者。

在你的代码中,callCount()从同步开始运行到await task,然后返回到Main()方法,由于你没有等待该方法完成,程序就会在方法count()未能完成之前结束。

通过将返回类型更改为Task并在Main()方法中调用Wait(),您可以看到期望的行为。

static void Main(string[] args)
{
    callCount().Wait();
}

static void count()
{
    for (int i = 0; i < 5; i++)
    {
        System.Threading.Thread.Sleep(2000);
        Console.WriteLine("count loop: " + i);
    }
}

static async Task callCount()
{
    Task task = new Task(count);
    task.Start();
    for (int i = 0; i < 3; i++)
    {
        System.Threading.Thread.Sleep(1000);
        Console.WriteLine("Writing from callCount loop: " + i);
    }
    Console.WriteLine("just before await");
    await task;
    Console.WriteLine("callCount completed");
}

编辑: 这是您的代码执行过程:

(为更好地理解,让我们将CallCount()的返回类型更改为Task

  1. 程序从Main()方法开始。
  2. 调用CallCount()方法。
  3. 任务在同一线程中创建。
  4. 然后启动任务。此时,将创建一个新线程,并行运行Count()方法。
  5. CallCount()中继续执行,执行for循环并打印“just before await”。
  6. 然后到达await task;。此时,异步等待模式发挥其作用。 await不像Wait(),它不会阻塞当前线程直到任务完成,而是将执行控制返回到Main()方法,并且所有剩余的指令在CallCount()中执行(在本例中只有Console.WriteLine("callCount completed");)将在任务完成后执行。
  7. Main()中,对CallCount()的调用将返回一个Task(包括CallCount()的剩余指令和原始任务),并且执行将继续进行。
  8. 如果您不等待此任务完成,则Main()中的执行将继续完成程序并销毁任务。
  9. 如果您调用Wait()(如果CallCount()是void,则没有要等待的任务),则让任务完成,保持在Main()中进行Count()执行,并打印“callCount completed”。

如果您希望在CallCount()中等待计数任务完成,而不返回到Main()方法,请调用task.Wait();,整个程序都将等待Count(),但这不是await的作用。

链接详细解释了异步等待模式。

希望这个代码工作流程图对您有所帮助。

enter image description here


是的,我在看到Stack Overflow上另一个类似问题和回答后,尝试了这种方式,但它仍然会出现相同的问题。我确实像你一样使用了Task作为返回类型。 - jaykum
我看到我的两个Writelines都在执行,但好像它忽略了“await task”。难道它不应该等到计数完成才退出callCount吗? - jaykum
当您执行异步方法时,它会同步开始运行直到达到await语句,然后代码的其余部分异步执行,并返回给调用者。但是这似乎不正确,因为一旦我执行了task.Start(),我会看到交替的Writeline执行,就好像count()和callCount()循环都在运行一样。当我等待任务时,我的程序退出了count()。 - jaykum
@jaykum 我编辑了我的答案,展示了异步等待模式在你的代码中的工作方式。当我说方法同步运行时,我指的是Main()和CallCount()方法,而不是任务。 - Arturo Menchaca
1
非常感谢Arturo对你详细的解释和帮助。我终于理解了基本概念,并成功让我的程序运行起来了。掌握await和wait()之间的关键区别非常重要。在你的帮助下,我已经进行了一些实验。我真的非常感激stackoverflow上的每个人如此慷慨地付出时间来帮助他人。这是一个伟大的社区。 - jaykum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接