-1

I want to make a C# Console App, that can evaluate user input while it is doing some work. For that I want to await the Input asynchron similar to this: await Console.ReadLine(). For testing purpose I simply want the main work loop to stop running when I hit Enter. I have achieved that like this:

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;
    }
}

This works fine and prints Ticks every Second until Enter is hit. My Qustion is now: Why does it not work when I delete this one Line? await Task.Delay(1); // <--WHY??? Whithout this Line the programm does nothing until I hit return, and then obviously never enters the while loop. How can this behavior be explained?

This suggestion Why does Console.In.ReadLineAsync block? explaines why Console.In.ReadLineAsyncisn't behaving as expected when my questionable Line is removed. But it does not explain why it actualy beahaves as expected when the Line is added.

  • It kinda answers why `Console.In.ReadLineAsync()` isn't behaving like expected, when my line in Question is removed. But not why it actualy behaves like I expect when the Line is in. – SlenderPaul May 11 '21 at 17:49
  • That is because when the line is not in there everything runs on the same thread. Therefore the read operation is blocking. If it is in there, the runtime decides to run it on two different threads. Just log the Thread.CurrentThread.ManagedThreadId. Guess that comes down to optimization in the runtime. If it would not block, there should be no reason for a deadlock. – Andreas Huber May 11 '21 at 17:52
  • Ah so an actualy working await causes the the async function to be pushed to a different thread! Thx that explains everything. – SlenderPaul May 11 '21 at 17:55
  • Well in this case yes - I would not vouch that this is always the case. If the read method would be truly async it would also not be a problem if everything runs on the same thread. – Andreas Huber May 11 '21 at 18:01
  • You might also look into `static async Task Main` -- it may help – Brian May 11 '21 at 18:49

2 Answers2

0

Here await Task.Delay(1); making sure that your program enters into the while loop. If you don't use this, the function waits for an input. When the input is given, the boolean value of running becomes false and the program never gets into the loop.

I don't know why on earth await Console.In.ReadLineAsync(); is blocking for an input. Maybe it's a bug. ‍♂️

A simple debugging process like this can help to understand the situation.

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");
    }
}
Mahmudul Hasan
  • 798
  • 11
  • 35
  • Yeah turns out the `await Console.In.ReadLineAsync();` is bugged and actualy blocks. But the reason why it doesnt block when the other await is put in front of it, is that it causes the async function to be put on a new thread. – SlenderPaul May 11 '21 at 18:02
  • Yeah. The bug was reported in 2013. I wonder why it's not fixed yet! ‍♂️ – Mahmudul Hasan May 11 '21 at 18:17
0

Thx to Andreas Huber for giving the reason. Console.In.ReadLineAsync() is bugged and actualy blocking until Input is submited. But the questionable Line pushes the async function to a new Thread. Testing the code like this:

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;
    }
} 

results in output: (you can see at which point i hit return)

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.

but with the line removed gives output like this:

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

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