10

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.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • 1
    Based on my experimentation there is some initialization that takes place the first time the console is written to that sets up the framework for concurrent reads and writes. If the console has been written to at all before you first call `ReadKey` you'll be fine, but it has never been written to this particular behavior is seen. As you said, easy to work around, but very curious. – Servy Feb 28 '13 at 19:36
  • What happens when you explicitly force Console.Out to flush? – Krypes Feb 28 '13 at 19:36
  • @Krypes The `Console.WriteLine` blocks (based on my experimentation), rather than continuing on and not doing anything, so there is no opportunity to flush it. – Servy Feb 28 '13 at 19:37
  • @Servy I used his code verbatim and it doesn't have the same issues he is encountering. This is with the WriteLine method removed. – Justin Feb 28 '13 at 19:38
  • @Justin I was able to reproduce it exactly as described. It may be a version dependent bug. – Servy Feb 28 '13 at 19:39
  • @Servy: You're right - the `Console.WriteLine` is blocking (it reaches it in the debugger) and never returning until I hit a key. – Matthew Watson Feb 28 '13 at 19:41
  • @Matthew Watson Does the issue still occur if you release build it and run it without the debugger attached? – Justin Feb 28 '13 at 19:43
  • @Justin I can repo without debugger in release build. – Servy Feb 28 '13 at 19:46
  • I can't reproduce the problem, it seems like a version dependent problem. – Nikola Davidovic Feb 28 '13 at 19:47

4 Answers4

19

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.

Steven Padfield
  • 644
  • 1
  • 5
  • 12
5

It is confirmed internal bug in .NET 4.5. It has been reported for example here: https://connect.microsoft.com/VisualStudio/feedback/details/778650/undocumented-locking-behaviour-in-system-console

This worked in .NET 3.5 and .NET 4.

More info: http://blogs.microsoft.co.il/blogs/dorony/archive/2012/09/12/console-readkey-net-4-5-changes-may-deadlock-your-system.aspx

You can use simple workaround to init internal structures and avoid blocking. Just add this to the beggining (from @renestein):

Console.Error.WriteLine(); 
Console.WriteLine(); 
  • This needs more upvotes! The call to Console.WriteLine does not appear to be sufficient in my case, so I couldn't understand why everyone else seemed happy. – Benjol Sep 29 '20 at 09:08
  • Something else which blocks Console.WriteLine is clicking in the console window (in Windows 10). This can *seem* like the same bug, because hitting a key unblocks it. – Benjol Oct 23 '20 at 09:06
0

This is probably occurring BECAUSE it is multi-threaded. Your main thread is moving on and exiting before your async task has a chance to report back. When the main thread exits, all child threads are killed.

What if you place a waiting before the ReadKey? does it output it correctly?

Justin
  • 3,337
  • 3
  • 16
  • 27
  • The OP's expected result is indeed what he should be seeing, he shouldn't need to modify the code at all. While the `ReadKey` is blocking the main thread the background thread should be able to write to the console. – Servy Feb 28 '13 at 19:29
  • You mean a thread.sleep()? I'll try it... [EDIT] It does make a difference (although it shouldn't) so I'll update my OP with the info. – Matthew Watson Feb 28 '13 at 19:29
0

Further to my comment, this can happen if 'QuickEdit' (a windows console function, not .NET) is enabled. In this case, clicking in the console window will block Writes.

Clicking outside the window, hitting escape or disabling QuickEdit fixes this:

Related: How and why does QuickEdit mode in Command Prompt freeze applications?

Benjol
  • 63,995
  • 54
  • 186
  • 268