Strange behaviour of Console.ReadKey() with multit

2020-01-25 02: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.

回答1:

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.



回答2:

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(); 


回答3:

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?