【问题标题】:Program exits upon calling await调用 await 时程序退出
【发布时间】:2015-12-19 21:06:41
【问题描述】:

我有一个while-loop,它应该重复程序直到满足某个条件。在这个循环中,我调用了一个async 函数,它会为我打印一条消息。这是(短)代码:

private void InitializeMessageSystem ( ) 
{
    do
    {
        // Do stuff
        await printMessage ("Hello World!");
        Console.ReadKey();
    } while (condition != true)
}

这里是函数PrintMessage()

private static async Task PrintMessage (string message, int spd = 1)
{
    int delay = 50 / spd;

    string[] words = message.Split(' ');

    int index = 1;

    for (int word = 0; word < words.Length; word++)
    {
        char[] current = words[word].ToCharArray();
        if (index + current.Length > Console.WindowWidth)
        {
            Console.WriteLine();
            index = 1;
        }
        for (int c = 0; c < current.Length; c++)
        {
            Console.Write(current[c]);
            await Task.Delay(delay);
        }
        Console.Write(" ");
    }
}

编辑:这是来自主函数的调用:

static void Main (string[] args) 
{
    InitializeMessageSystem();
    Console.ReadKey();
}

问题

为什么我的程序在功能尚未完成的情况下按了一个键会退出?我以为程序会等待Console.ReadKey(),直到函数PrintMessage()完成?

【问题讨论】:

  • 你的程序的入口点在哪里?由于 Main 不能是异步的,因此您显示的代码是另一种方法。很有可能,主线程进入该方法,等待等待并在另一个线程上恢复。该线程是被 ReadKey 阻止的线程。主线程继续并退出导致程序退出。
  • @mike z 入口点只有一行代码,循环所在函数的调用
  • @IanH。然而,其中的细节是高度相关的,即使它只是一行代码。
  • @Ian H.:正如上面所暗示的,缺少的几行(主要方法+“循环”方法的签名)是解释您的“奥秘”的关键。请张贴。提出问题时,不要只显示认为问题所在的代码。通常,这种假设是错误的,这就是为什么首先提出这个问题的原因。相反,请始终提供一个最小但完整的程序,以便我们重现您的问题。如此简单,却又如此有效地获得任何问题的正确答案。

标签: c# async-await


【解决方案1】:

您的问题是await 将程序的控制流返回给函数的调用者。通常,当您等待的异步任务完成时,会继续执行。

因此,当您等待printMessagemain 现在等待按键输入时,控制权将返回到您的主函数。当您按下键 main 时返回操作系统,您的进程(包括所有异步任务)终止。

将您的 InitializeMessageSystem 更改为

private async Task InitializeMessageSystem ( )  

并将main中的代码更改为

InitializeMessageSystem().Wait();

等到InitializeMessageSystem 完全完成后再等待密钥。

【讨论】:

  • 现在的问题是,如果我在函数执行过程中按下一个键,它会立即继续,而无需等待函数完成。
  • 当然,因为密钥在你读取它之前是被缓冲的。我没有即时答案。尝试用谷歌搜索 flush 之类的东西,但我记得刷新输入流有点困难。
  • 我必须在等待之前调用它,否则控制台将结束
  • await 在完成之前不会将程序的控制权返回给调用者。 Main 在等待 printMessage 时从不等待按键。实际的问题是等待任务的代码不会阻止程序退出。
【解决方案2】:

以下代码执行时没有任何错误或警告。但是当您执行代码时,程序会静默退出。您可能期望的是程序等待任务完成,要求用户按任意键并退出。实际发生的情况是在执行完 await 语句后,控制返回到调用步骤。在我们的例子中,在执行步骤 await task; 之后,在任务完成之前,控制返回到调用步骤 SomeTask(); 并且程序退出。

class Program
{
    static void Main(string[] args)
    {
        SomeTask();
    }

    public static async void SomeTask()
    {
        Task task = Task.Run(() =>
        {
            System.Threading.Thread.Sleep(20000);
            Console.WriteLine("Task Completed!");
        });
        await task;
        Console.WriteLine("Press any key to exit");
        Console.ReadLine();
    }
}

要解决此问题,请将 await 添加到 SomeTask(); 调用,以便程序等待异步 SomeTask() 完成。您还应该更改返回类型 SomeTask() 从 void 到 Task。

class Program
{
    static void Main(string[] args)
    {
        await SomeTask();
    }

    public static async Task SomeTask()
    {
        Task task = Task.Run(() =>
        {
            System.Threading.Thread.Sleep(20000);
            Console.WriteLine("Task Completed!");
        });
        await task;
        Console.WriteLine("Press any key to exit");
        Console.ReadLine();
    }
}

【讨论】:

    【解决方案3】:

    调用同步函数和异步函数的区别在于,当调用同步函数时,你知道当你到达调用后的语句时,函数被完全执行。调用异步函数时,该函数被调度为线程池中的一个任务,当池中的任何线程有时间时执行。

    这让您有时间在其中一个线程执行任务时做其他事情。只要您需要结果,就等待任务完成。

    这仅在您的函数也是异步的情况下才有效。如果你不让你的函数异步,你就不能使用 async-await。使您的函数异步并且您的函数的客户端也异步是真正使用 async-await 的唯一方法。请记住:所有异步函数都应返回 Task 而不是 void 和 Task&lt;TResult> 而不是 TResult。唯一的例外是事件处理程序

    private async void Button1_Clicked(object sender, ...)
    {
        var myTask = SlowMultiplierAsync(4, 3);
        // while myTask is running you can do other things
        // after a while you need the result:
        int taskResult = await myTask;
        Process(taskResult);
    }
    
    private async Task<int> SlowMultiplierAsync(int x, int y)
    {
         // let's take a nap before multiplying
         // do this by awaiting another async function:
         await Task.Delay(TimeSpan.FromSeconds(5));
         return x * y;
    }
    

    如果您不想(或不能)使您的函数异步,您可以通过使用 task.Run 启动任务来模拟类似的行为:

    private void InitializeMessageSystem ( ) {
    do
    {
        // Do stuff
        var myTask = task.Run( () => printMessage ("Hello World!"));
        myTask.Wait();
        Console.ReadKey();
    } while (condition != true)
    

    虽然这将确保您的函数在任务完成之前不会结束,但您的函数对于您的客户来说将是一个同步的。例如,它不会使您的 UI 响应。

    Eric lippert 在 stackoverflow 上曾经向我解释了异步并发之间的区别。 Link to his answer

    假设你必须做早餐。烤一些面包,煮一些鸡蛋。

    • 同步:开始烤面包。等到烘烤结束。开始煮鸡蛋,等鸡蛋煮熟。
    • 异步但非并发:开始烤面包,烤面包时开始煮鸡蛋。在煮鸡蛋的同时,您还可以做其他事情,例如泡茶。过了一会儿,你等到鸡蛋煮熟,然后等到面包烤好。这通常是异步等待。
    • 异步和并发:雇一个厨师烤面包,雇一个厨师煮鸡蛋,然后等到两个厨师都完成。在这里,烘烤和烹饪是由不同的线程完成的。这是最昂贵的方法

    【讨论】:

    • "当调用异步函数时,该函数被调度为线程池中的一个任务,当池中的任何线程有时间时执行它。"我看到你相信有一根线的神话。虽然通常有一个线程,并且通常应该有一个线程(因为工作是处理器绑定的),但没有要求有一个线程。很多异步工作可以在没有额外线程的情况下完成,比如说,它是 I/O 绑定的。您可能想阅读 Stephen Cleary 关于该主题的文章。 blog.stephencleary.com/2013/11/there-is-no-thread.html
    猜你喜欢
    • 2023-03-16
    • 2013-08-28
    • 1970-01-01
    • 2022-10-06
    • 2021-11-12
    • 2023-03-23
    • 1970-01-01
    • 2021-02-08
    • 1970-01-01
    相关资源
    最近更新 更多