【问题标题】:C# Async Console Input Behaviour [duplicate]C#异步控制台输入行为[重复]
【发布时间】:2021-05-12 02:37:41
【问题描述】:

我想制作一个 C# 控制台应用程序,它可以在执行某些工作时评估用户输入。为此,我想等待类似于此的输入异步:await Console.ReadLine()。出于测试目的,我只是希望主工作循环在我按 Enter 时停止运行。我是这样实现的:

using System;
using System.Threading;
using System.Threading.Tasks;


class WorkTillEnter
{
    private static bool running = false;

    public static void Main(string[] args)
    {
        WorkTillEnter.running = true;

        WorkTillEnter.observeInputAsync();

        DateTime lastTick = DateTime.Now;

        while(WorkTillEnter.running){
            if(lastTick.Second != DateTime.Now.Second){
                Console.WriteLine($"Tick {DateTime.Now}");
                lastTick = DateTime.Now;
            }
            //Doing Work in this loop until enter is hit
        }

        Console.WriteLine("Worker Terminated.");
    }

    private static async void observeInputAsync(){
        await Task.Delay(1); //  <--WHY???
        await Console.In.ReadLineAsync();
        WorkTillEnter.running = false;
    }
}

这可以正常工作,并且每秒打印一次 Ticks,直到按下 Enter。 我现在的问题是:为什么我删除了这一行后不起作用? await Task.Delay(1); // &lt;--WHY??? 没有这一行,程序什么也不做,直到我点击返回,然后显然永远不会进入 while 循环。如何解释这种行为?

这个建议Why does Console.In.ReadLineAsync block? 解释了为什么当我的可疑线路被删除时Console.In.ReadLineAsync 的行为不符合预期。但它没有解释为什么它在添加 Line 时实际上表现得如预期。

【问题讨论】:

  • 这有点回答了为什么Console.In.ReadLineAsync() 的行为不像预期的那样,当我在问题中的行被删除时。但不是为什么它实际上表现得像我预期的那样。
  • 那是因为当该行不在其中时,所有内容都在同一个线程上运行。因此读操作是阻塞的。如果它在那里,运行时决定在两个不同的线程上运行它。只需记录 Thread.CurrentThread.ManagedThreadId。猜猜这归结为运行时的优化。如果它不会阻塞,则应该没有死锁的理由。
  • 啊,所以实际工作的等待会导致异步函数被推送到不同的线程!谢谢,解释了一切。
  • 好吧,在这种情况下是的 - 我不保证情况总是如此。如果 read 方法真的是异步的,那么如果所有东西都在同一个线程上运行,那也不是问题。
  • 你也可以看看static async Task Main——它可能会有所帮助

标签: c# asynchronous async-await console-application user-input


【解决方案1】:

这里await Task.Delay(1); 确保您的程序进入while 循环。如果您不使用它,该函数将等待输入。给定输入后,running 的布尔值变为false,程序永远不会进入循环。

我不知道为什么 await Console.In.ReadLineAsync(); 会阻止输入。也许这是一个错误。 ??‍♂️

这样一个简单的调试过程可以帮助理解情况。

using System;
using System.Threading.Tasks;


class WorkTillEnter
{
    private static bool running;

    public static void Main(string[] args)
    {
        Console.WriteLine("a");
        running = true;
        Console.WriteLine("b");

        ObserveInputAsync();
        Console.WriteLine("c");

        DateTime lastTick = DateTime.Now;
        Console.WriteLine("d");

        while (running)
        {
            Console.WriteLine("e");
            if (lastTick.Second != DateTime.Now.Second)
            {
                Console.WriteLine($"Tick {DateTime.Now}");
                lastTick = DateTime.Now;
            }
            //Doing Work in this loop until enter is hit
        }

        Console.WriteLine("f");
        Console.WriteLine("Worker Terminated.");
    }

    private static async void ObserveInputAsync()
    {
        Console.WriteLine("1");
        await Task.Delay(1); //  <--WHY???
        Console.WriteLine("2");
        await Console.In.ReadLineAsync();
        running = false;
        Console.WriteLine("3");
    }
}

【讨论】:

  • 是的,原来await Console.In.ReadLineAsync(); 被窃听并且实际上被阻塞了。但是当另一个await放在它前面时它没有阻塞的原因是它导致async函数被放在一个新线程上。
  • 是的。该错误是在 2013 年报告的。我想知道为什么它还没有修复! ??‍♂️
【解决方案2】:

感谢 Andreas Huber 给出原因。 Console.In.ReadLineAsync() 被窃听并且实际上阻塞直到输入被提交。但是有问题的 Line 将 async 函数推送到一个新线程。像这样测试代码:

using System;
using System.Threading;
using System.Threading.Tasks;


class WorkTillEnter
{
    private static bool running = false;

    public static void Main(string[] args)
    {
        WorkTillEnter.running = true;

        Console.WriteLine($"before Asnyc called: {Thread.CurrentThread.ManagedThreadId}");
        WorkTillEnter.observeInputAsync();
        Console.WriteLine($"after Asnyc called: {Thread.CurrentThread.ManagedThreadId}");

        DateTime lastTick= DateTime.Now;
        while(WorkTillEnter.running){
            if(lastTick.Second != DateTime.Now.Second){
                Console.WriteLine($"Tick {DateTime.Now}");
                lastTick = DateTime.Now;
            }
            //Doing Work in this loop until enter is hit
        }

        Console.WriteLine("Worker Terminated.");
    }

    private static async void observeInputAsync(){
        Console.WriteLine($"Async before await: {Thread.CurrentThread.ManagedThreadId}");
        await Task.Delay(1); //  <--WHY???
        Console.WriteLine($"Async after first await: {Thread.CurrentThread.ManagedThreadId}");
        await Console.In.ReadLineAsync();
        Console.WriteLine($"Async after second await: {Thread.CurrentThread.ManagedThreadId}");
        WorkTillEnter.running = false;
    }
} 

输出结果:(你可以看到我在哪一点点击返回)

before Asnyc called: 1
Async before await: 1
after Asnyc called: 1
Async after first await: 4
Tick 11.05.2021 20:30:42
Tick 11.05.2021 20:30:43

Async after second await: 4
Worker Terminated.

但删除该行后会给出如下输出:

before Asnyc called: 1
Async before await: 1
Async after first await: 1

Async after second await: 1
after Asnyc called: 1
Worker Terminated.

【讨论】:

    猜你喜欢
    • 2016-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-01
    • 2018-08-08
    • 1970-01-01
    相关资源
    最近更新 更多