当我使用线程将内容打印到控制台时,为啥会产生奇怪的结果?

Posted

技术标签:

【中文标题】当我使用线程将内容打印到控制台时,为啥会产生奇怪的结果?【英文标题】:When I use threads to print things to the console, why does it produce odd results?当我使用线程将内容打印到控制台时,为什么会产生奇怪的结果? 【发布时间】:2013-05-07 08:43:39 【问题描述】:

我最近在阅读 Rob Miles (here) 的非常好的 pdf 文件时进入了 Threads。他在第 160 页上有一个示例(2012,C# pdf),但它没有写入控制台,只是做了空循环。

我编写了一个非常简单的线程生成循环,它创建了 10 个线程,这些线程在 1000 的每个倍数上将它们的 ID 写入屏幕。这很好 - 它向我展示了线程是如何一起运行的。我的问题始于为什么我的输出如此混乱?通常当我运行下面的程序时,我会得到多个“线程 3 完成”行,我很确定,我应该只有一个。

我在循环中添加了来自 MSDN 的 "lock",但它似乎仍然会产生奇怪的输出(我将在下面举一个示例)。

    namespace ConsoleApplication1
    
        class Program
        
            static object sync = new object();

            static void Main(string[] args)
            
                for (int i = 0; i < 10; i++)
                
                    Thread myThread = new Thread(() => DoThis(i));
                    myThread.Start();
                
                Console.ReadLine();
            

            static void DoThis(int s)
            
                lock (sync) // a new addition that hasn't helped
                
                    for (long j = 0; j < 10000; j++)
                    
                        if (j % 1000 == 0) Console.Write(String.Format("0.", s));
                    
                    Console.WriteLine("\r\nThread 0 Finished", s);
                    Debug.Print("\r\nThread 0 Finished", s); //<-- added to debug

                
            
        
    

我以为我做得很好 - 我有局部变量(我的计数循环),我有一个可能不是通过引用传递的 int,后来我尝试在循环时锁定它。不高兴。我需要做什么才能使输出看起来合理?我尝试使用 Deubg.Print 进行故障排除,但它也有错误(如下)。

最后,我想在一个更大的应用程序中使用线程,但如果我不能在这里得到它,我不确定我是否想要!

debug.print 行末尾的示例输出:(注意倍数)...

Thread 1 Done
The thread '<No Name>' (0x15cc) has exited with code 0 (0x0).

Thread 9 Done
The thread '<No Name>' (0x1d0c) has exited with code 0 (0x0).

Thread 6 Done
The thread '<No Name>' (0x2248) has exited with code 0 (0x0).

Thread 10 Done
The thread '<No Name>' (0x22bc) has exited with code 0 (0x0).

Thread 9 Done
The thread '<No Name>' (0x85c) has exited with code 0 (0x0).

Thread 9 Done
The thread '<No Name>' (0x1628) has exited with code 0 (0x0).

Thread 3 Done
The thread '<No Name>' (0x2384) has exited with code 0 (0x0).

Thread 6 Done

Thread 2 Done

Thread 4 Done
The thread '<No Name>' (0x2348) has exited with code 0 (0x0).
The thread '<No Name>' (0x2420) has exited with code 0 (0x0).
The thread '<No Name>' (0x1ea8) has exited with code 0 (0x0).

如果我能提供更多关于我尝试过的信息,请告诉我。

【问题讨论】:

我认为你在循环中的变量被捕获了。将 i 存储在临时变量中。但是,如果您的意思是为什么输出没有排序..线程的执行不能保证按照它开始的顺序。 不担心确切的顺序,但看起来@MatthewWatson 给出了相同的答案......谢谢。 C# Captured Variable In Loop的可能重复 【参考方案1】:

尝试使用Parallel.For,而不是自己在循环中启动线程:

Parallel.For(0, 10, DoThis);

如果您需要传递一个额外的参数(比如w)作为其中的一部分,那么您可以这样做:

var w = 4;
Parallel.For(0, 10, i => DoThis(i, w));

当然要考虑的另一件事是Console 对象是独占的。如果你从一个线程中使用它,任何其他尝试使用它的线程都将被阻塞,直到它完成。

您的lock (sync) 也将阻止任何两个线程同时进行操作。

请记住,您的线程不能保证以任何特定顺序执行。

【讨论】:

我真的不介意什么顺序 - 锁是我试图找出问题所在!我从未使用过 Parallel.For(以及许多其他的东西),所以请快速阅读。 我还能将变量传递给 Dothis 方法吗? Parallel.For 将索引 (0..9) 作为参数传递给 Dothis - Dothis 恰好与它所期望的委托类型相匹配,所以我给你的应该是工作” 对,你是......就在我让你“上钩”时,如果我的方法是 DoWork(int s, int w)...,你将如何修改它? 很酷,谢谢 - 现在我看到了,很简单。如果可以的话,我会再给你一个赞!【参考方案2】:

您的问题是您在循环变量上使用了“修改后的闭包”。

尽管foreach 循环已解决此问题,但for 循环仍然存在问题(并且永远存在)

要修复它,请将 Main() 更改为:

static void Main(string[] args)

    for (int i = 0; i < 10; i++)
    
        int copy = i; // Make a copy of i to prevent the "modified closure" problem.
        Thread myThread = new Thread(() => DoThis(copy));
        myThread.Start();
    
    Console.ReadLine();

更多详情请看这里:

Access to Modified Closure

Eric Lippert's Article on Closures

【讨论】:

好吧,我正怀疑地看着那个“i”……让我快速玩一下。 @IV4 for 循环的闭包没有改变,只有foreach 循环。见***.com/questions/16264289/… @I4V 这与 .NET 4.5 和 C# 5 相同。循环 for (int i = 0; i &lt; 10; i++) Thread myThread = new Thread(() =&gt; DoThis(i)); myThread.Start(); 将变量 i 作为“外部”变量,就像它曾经是 int i; /* declared before loop */ for (i = 0; i &lt; 10; i++) Thread myThread = new Thread(() =&gt; DoThis(i)); myThread.Start(); 一样。跨度> ...但是,如果他使用foreach (var i in Enumerable.Range(0, 10)) Thread myThread = new Thread(() =&gt; DoThis(i)); myThread.Start(); ,那将取决于他使用的 C# 版本。 谢谢大家! - 似乎我只需要知道要搜索什么!我可以看到线程是一个非常有趣的挑战。

以上是关于当我使用线程将内容打印到控制台时,为啥会产生奇怪的结果?的主要内容,如果未能解决你的问题,请参考以下文章

当我从线程打印时,它使用 ncurses 在 C 中给了我奇怪的输出

为啥我的链表内容在退出函数时会消失?

int的打印数组会在ada中生成奇怪的输出

当我尝试在 .NET Core 中反序列化 FormFile 对象时,为啥会出现奇怪的异常?

为啥在多维数据集中分配值时会产生nan?

为啥我的程序没有将字符从文件输入到二维数组中?