10

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

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

用代码最容易解释这一点:

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

我觉得这种行为很神秘。这很容易解决,但我对它为什么会发生很感兴趣。

[编辑]

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

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

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

构建配置

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

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

4

4 回答 4

19

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

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

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

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

于 2013-02-28T20:06:22.767 回答
5

已确认 .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(); 
于 2013-04-22T13:55:24.197 回答
0

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

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

于 2013-02-28T19:27:46.740 回答
0

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

在窗口外单击、按转义键或禁用 QuickEdit 可修复此问题:

相关:命令提示符中的 QuickEdit 模式如何以及为什么会冻结应用程序?

于 2021-03-22T15:46:26.277 回答