带有多线程的 Console.ReadKey() 的奇怪行为

Posted

技术标签:

【中文标题】带有多线程的 Console.ReadKey() 的奇怪行为【英文标题】:Strange behaviour of Console.ReadKey() with multithreading 【发布时间】:2013-02-15 03:20:56 【问题描述】:

在多线程程序中使用Console.ReadKey() 时遇到一个奇怪的问题。

我的问题是:为什么会这样?这是一个错误,还是因为我滥用Console? (请注意,控制台应该是线程安全的,according to the documentation。)

用代码最容易解释:

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.");
        
    

如果你运行它并且按任何键,你认为会打印出什么?

答案正是你所期望的:

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

现在注释掉Console.WriteLine("X") 并再次运行它(不按任何键)。 我希望看到这个输出:

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

相反,我看到什么都没有。然后当我按下一个键时,它会说:

Entering the test() function.

...就是这样。程序退出(当然)并且没有时间进入下一个WriteLine()

我觉得这种行为很神秘。这很容易解决,但我很好奇为什么会这样。

[编辑]

如果我在Console.ReadKey() 之前添加一个Thread.Sleep(1),它会按预期工作。当然,这不是必需的,因为 Console.ReadKey() 无论如何都应该永远等待。

所以看起来它可能是某种竞争条件?

更多信息:Servy 发现(并且我已经复制)Console.WriteLine("Entering the test() function.") 行被阻塞,直到按下任何键。

构建配置

Visual Studio 2012,Windows 7 x64,四核,英语(英国)。

我尝试了 .Net4、.Net4.5、x86、AnyCPU 以及调试和发布的所有组合,但它们都不能在我的 PC 上运行。但是发生了一件非常奇怪的事情。当我第一次尝试 .Net4 的 AnyCPU 版本时它开始工作,但随后又停止工作。看起来很像只影响某些系统的竞争条件。

【问题讨论】:

根据我的实验,第一次写入控制台时会进行一些初始化,以设置并发读写的框架。如果在您第一次调用ReadKey 之前已经写入了控制台,那么您会没事的,但它从未被写入过这种特殊的行为。正如你所说,很容易解决,但很好奇。 当你明确强制 Console.Out 刷新时会发生什么? @Krypes Console.WriteLine 阻塞(基于我的实验),而不是继续进行而不做任何事情,因此没有机会刷新它。 @Servy 我逐字使用了他的代码,它没有遇到他遇到的相同问题。这是删除了 WriteLine 方法。 @Justin 我能够完全按照描述复制它。这可能是版本相关的错误。 【参考方案1】:

这是一个竞争条件。这是第一个 Console.WriteLine 不存在时发生的情况:

    任务已创建,但未运行 Console.ReadKey 执行,锁定 Console.InternalSyncObject,并阻塞等待输入 Task 的 Console.WriteLine 调用 Console.Out,Console.Out 调用 Console.InitializeStdOutError 进行首次初始化以设置控制台流 Console.InitializeStdOutError 尝试锁定 Console.InternalSyncObject,但 Console.ReadKey 已经拥有它,因此它会阻塞 用户按下一个键,Console.ReadKey 返回,解除锁定 对 Console.WriteLine 的调用已解除阻塞并完成执行 进程退出,因为 ReadKey 调用后 Main 中没有任何内容 Task 中的剩余代码没有机会运行

Console.WriteLine 留在其中时行为不同的原因是因为对 Console.InitializeStdOutError 的调用没有与 Console.ReadKey 并行发生。

所以简短的回答是:是的,您正在滥用控制台。您可以自己初始化控制台(通过取消引用 Console.Out),或者在启动 Task 之后,但在 ReadKey 之前等待事件,然后在第一次调用 Console.WriteLine 之后让 Task 发出事件信号。

【讨论】:

啊哈,所以它陷入了僵局。他们在等待 I/O 时持有锁有点淘气! :) 是的,不开玩笑!但是控制台在多线程上下文中是出了名的问题。【参考方案2】:

这是 .NET 4.5 中确认的内部错误。 例如这里已经报道过:https://connect.microsoft.com/VisualStudio/feedback/details/778650/undocumented-locking-behaviour-in-system-console

这适用于 .NET 3.5 和 .NET 4。

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

您可以使用简单的解决方法来初始化内部结构并避免阻塞。只需将其添加到开头(来自@renestein):

Console.Error.WriteLine(); 
Console.WriteLine(); 

【讨论】:

这需要更多的支持!在我的情况下,对 Console.WriteLine 的调用似乎不够,所以我不明白为什么其他人看起来都很开心。 阻止 Console.WriteLine 的其他内容正在控制台窗口中单击(在 Windows 10 中)。这可能看起来像同一个错误,因为按下一个键会取消阻止它。【参考方案3】:

这可能是因为它是多线程的。在您的异步任务有机会报告之前,您的主线程正在继续并退出。当主线程退出时,所有子线程都被杀死。

如果您在 ReadKey 之前放置一个等待会怎样?输出正确吗?

【讨论】:

OP的预期结果确实是他应该看到的,他根本不需要修改代码。当ReadKey 阻塞主线程时,后台线程应该能够写入控制台。 你的意思是thread.sleep()?我会试试...Edit 它确实会有所作为(尽管它不应该)所以我会用信息更新我的 OP。【参考方案4】:

根据我的评论,如果启用了“QuickEdit”(Windows 控制台功能,而不是 .NET),则可能会发生这种情况。在这种情况下,单击控制台窗口将阻止写入。

在窗口外单击、点击转义或禁用 QuickEdit 可以解决此问题:

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

【讨论】:

以上是关于带有多线程的 Console.ReadKey() 的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

多线程

winfrom 多线程 并发 和定时任务

当nohup遇到console.ReadKey()

C# Console.Read();和Console.ReadLine();和Console.ReadKey();区别详解。

如何将Console.Readkey转换为int c#

C#开启线程的四种方式