Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange behaviour of Console.ReadKey() with multithreading

I'm getting a weird problem when using Console.ReadKey() in a multithreaded program.

My question is: Why is this happening? Is it a bug, or is it because I am abusing Console? (Note that Console is supposed to be threadsafe, according to the documentation.)

It's easiest to explain this with code:

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

namespace ConsoleApplication2
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("X");  // Also try with this line commented out.
            Task.Factory.StartNew(test);
            Console.ReadKey();
        }

        private static void test()
        {
            Console.WriteLine("Entering the test() function.");
            Thread.Sleep(1000);
            Console.WriteLine("Exiting the test() function.");
        }
    }
}

What do you think that will print out if you run it and don't press a key?

The answer is just what you'd expect:

X
Entering the test() function.
Exiting the test() function.

Now comment out the Console.WriteLine("X") and run it again (without pressing a key). I expected to see this output:

Entering the test() function.
Exiting the test() function.

Instead, I see nothing. Then when I press a key, it says:

Entering the test() function.

...and that's it. The program exits (of course) and it doesn't have time to get to the next WriteLine().

I find this behaviour very mysterious. It's easy to work around, but I'm intrigued as to why it happens.

[EDIT]

If I add a Thread.Sleep(1) immediately before Console.ReadKey() it does work as expected. Of course, this shouldn't be necessary since the Console.ReadKey() should wait forever anyway.

So it's looking like it might be some kind of race condition?

More info: Servy has found (and I have duplicated) that the line Console.WriteLine("Entering the test() function.") is blocking until any key is pressed.

Build Configuration

Visual Studio 2012, Windows 7 x64, Quad Core, English (UK).

I've tried all combinations of .Net4, .Net4.5, x86, AnyCPU and debug and release, and none of them work on my PC. But a really weird thing happened. It started working when I first tried the AnyCPU version for .Net4, but then it stopped working again. Seems very much like a race condition that only affects some systems.

like image 442
Matthew Watson Avatar asked Feb 28 '13 19:02

Matthew Watson


1 Answers

This is a race condition. Here is what's happening when the first Console.WriteLine is not there:

  1. Task is created, but not run
  2. Console.ReadKey executes, takes a lock on Console.InternalSyncObject, and blocks waiting for input
  3. The Task's Console.WriteLine calls Console.Out, which calls Console.InitializeStdOutError for first-time initialization to set up the console streams
  4. Console.InitializeStdOutError attempts to lock on Console.InternalSyncObject, but Console.ReadKey already has it, so it blocks
  5. The user presses a key and Console.ReadKey returns, releasing the lock
  6. The call to Console.WriteLine is unblocked and finishes executing
  7. The process exits, because there is nothing in Main after the ReadKey call
  8. The remaining code in the Task does not get a chance to run

The reason it behaves differently when the Console.WriteLine is left in there is because the call to Console.InitializeStdOutError is not happening in parallel with Console.ReadKey.

So the short answer is: yes you are abusing Console. You could either initialize the console yourself (by dereferencing Console.Out), or you would wait on an event after starting the Task, but before ReadKey, and then have the Task signal the event after calling Console.WriteLine the first time.

like image 50
Steven Padfield Avatar answered Oct 04 '22 03:10

Steven Padfield