使用 Console.Writeline() 或 Console.Write() 时,多线程 C# 控制台应用程序中不经常挂起

Posted

技术标签:

【中文标题】使用 Console.Writeline() 或 Console.Write() 时,多线程 C# 控制台应用程序中不经常挂起【英文标题】:Infrequent hangs in a multi-threaded C# console application when using Console.Writeline() or Console.Write() 【发布时间】:2011-06-27 20:45:18 【问题描述】:

我编写了一个控制台应用程序,它使用 console.write 和 console.writeline 来提供一些日志记录。该应用程序是一个服务器应用程序,它使用异步 beginacceptconnection() 和 beginread() ( Sockets ) 进行通信。有时我会收到关于它挂起的报告,并且从有限的调试中我可以看到问题是 Console.Writeline() 或 Console.write()。

由于是多线程的,我一直小心地在日志记录类周围加锁,这样只有一个线程可以一次记录一条消息.....当我遇到挂起时,我得到的只是线程阻塞在锁上和 VS 报告控件已传递到 Console.Write 并且它正在等待它回来......它永远不会。

几天前,我收到了另一份失败报告,但这次是在启动过程中......尚未启动任何异步连接(虽然主线程确实产生了一个线程来启动)并且我收到了一个图片.....见下文。(我添加了开始和结束关键部分行以防止这种情况,但它没有)

// Logging Class

public class Logging

    // Lock to make the logging class thread safe.
    static readonly object _locker = new object();

    public delegate void msgHandlerWriteLineDelegate(string msg, Color col);
    public static event msgHandlerWriteLineDelegate themsgHandlerWriteLineDelegate;

    public delegate void msgHandlerWriteDelegate(string msg, Color col);
    public static event msgHandlerWriteDelegate themsgHandlerWriteDelegate;

    public static void Write(string a, Color Col)
    
        if (themsgHandlerWriteDelegate != null)
        
            lock (_locker)
            
                themsgHandlerWriteDelegate(a, Col);
            
        
    

    public static void Write(string a)
    
        if (themsgHandlerWriteDelegate != null)
        
            lock (_locker)
            
                themsgHandlerWriteDelegate(a, Color.Black);
            
        
    

    public static void WriteLine(string a, Color Col)
    
        if (themsgHandlerWriteLineDelegate != null)
        
            lock (_locker)
            
                themsgHandlerWriteLineDelegate(a, Col);
            
        
    

    public static void WriteLine(string a)
    
        if (themsgHandlerWriteLineDelegate != null)
        
            lock (_locker)
            
                themsgHandlerWriteLineDelegate(a, Color.Black);
            
        
    

    // Console Methods That implement the delegates in my logging class.

    public static void ConsoleWriteLine(string message, Color Col)
    
        try
        
            if (Col == Color.Black)
            
                Console.ForegroundColor = ConsoleColor.Gray;
            
            else
            
                Console.ForegroundColor = (ConsoleColor)Enum.Parse(typeof(ConsoleColor), Col.Name);
            
            Thread.BeginCriticalRegion();
            Console.WriteLine(message);
            Thread.EndCriticalRegion();
            Console.ForegroundColor = ConsoleColor.Gray;
        
        catch (ThreadAbortException ex)
        
            Console.WriteLine("ThreadAbortException : " + ex.Message);
        
        catch (Exception ex)
        
            Console.WriteLine("Exception : " + ex.Message);
        
    

    public static void ConsoleWrite(string message, Color Col)
    
        try
        
            if (Col == Color.Black)
            
                Console.ForegroundColor = ConsoleColor.Gray;
            
            else
            
                Console.ForegroundColor = (ConsoleColor)Enum.Parse(typeof(ConsoleColor), Col.Name);
            
            Thread.BeginCriticalRegion();
            Console.Write(message);//**THIS IS WHERE IS HANGS...IT NEVER RETURNS **
            Thread.EndCriticalRegion();
            Console.ForegroundColor = ConsoleColor.Gray;
        
        catch (ThreadAbortException ex)
        
            Console.WriteLine("ThreadAbortException : " + ex.Message);
        
        catch (Exception ex)
        
            Console.WriteLine("Exception : " + ex.Message);
        
    

    public static void ConsoleUpdate(string message)
    
        try
        
            Thread.BeginCriticalRegion();
            Console.WriteLine(message);//**THIS IS WHERE IS HANGS...IT NEVER RETURNS **
            Thread.EndCriticalRegion();
        
        catch (ThreadAbortException ex)
        
            Console.WriteLine("ThreadAbortException : " + ex.Message);
        
        catch (Exception ex)
        
            Console.WriteLine("Exception : " + ex.Message);
        
    

    // The main method...subscribes to delegates and spawns a thread to boot HW..main thread then exits.

    public static void Main()
    
        Logging.themsgHandlerWriteDelegate += new Logging.msgHandlerWriteDelegate(ConsoleWrite);
        Logging.themsgHandlerWriteLineDelegate += new Logging.msgHandlerWriteLineDelegate(ConsoleWriteLine);
        Logging.themsgHandlerUpdateDelegate += new Logging.msgHandlerUpdateDelegate(ConsoleUpdate);
    


public class ClassOnOtherThread

    // In a different class running on a different thread the following line occasionly invokes the error:

    private void BootHw(string Resource, string Resource2)
    
        Logging.Write("\t\t[");
    

我对 MSDN 的阅读表明 Console.WriteLine 和 Console.Write 是线程安全的,因此我实际上不需要锁定它....我也不敢相信微软的代码是错误的 (;-) 和所以我猜这是我的代码正在执行的一些交互导致错误。

现在我的问题:我应该做些什么来防止 Console.WriteLine 和 Console.Write 被打断吗?...我猜是它打断了它...但我真的不知道!!

非常感谢任何帮助。

问候,

戈登。

【问题讨论】:

我明天一回到办公室就发一些。 按Ctrl+S,瞬间挂机。 看到代码后:有很多(真的)可以去掉。我无法发现明显的错误,但 Begin/EndCrtiticalRegion 并不是有意的,也不是 Console.WriteLine() 所需要的。您的错误可能在套接字代码中。 是的,开始/结束临界区方法确实是我尝试修复它,但它没有工作。我仍然很困惑套接字代码如何阻止 console.writeline() 你有多确定是WriteLine挂了? 【参考方案1】:

我遇到了同样的问题。

我在主线程中使用console.readkey() 来防止在调试模式下关闭应用程序。

用无限循环替换它后,我的问题就解决了。

【讨论】:

这对我有用...将 ReadKey 更改为 ReadLine 并且已修复。谢谢【参考方案2】:

您应该通过移除日志周围的锁来解决您的问题。日志记录是通过同步的(和线程安全的)Console.WriteLine 完成的。您可能通过自己的锁定机制导致死锁(尽管我无法在没有看到代码的情况下进行验证)。

【讨论】:

同意,但它没有解释为什么 console.writeline 或 console.write 方法一旦被调用就无法返回(从而造成你提到的死锁)......我只是在 VS 中得到一个绿色箭头旁边有一条消息说该方法被调用并且没有返回....如果我取消暂停 vs 并等待 30 秒....然后再次暂停它在同一个地方。问候, 死锁不是由 Console.WriteLine 返回失败造成的(这只是阻塞)。当线程争夺资源时会发生死锁。由于您尚未发布任何代码,因此我无法告诉您您在死锁什么。 console.writeline() 那么如何阻塞呢?代码现已发布。【参考方案3】:

我猜你的应用程序是由另一个重定向 stderr 和 stdout 的进程启动的。如果您的“观察者”进程对同一线程上的两个流使用ReadToEnd(),您可能会死锁。

另一个解决死锁的方法是通过标准输入发送子进程输入,这反过来又启动另一个进程,该进程有一个无限期地等待输入的控制台。 wmic.exe 在我身上发生过一次,它在 stdin 被重定向时会阻塞。

如果您使用过您的日志类,我怀疑您使用自己的日志类更改了 Console.Out 的底层 Stream。请至少发布您的应用程序挂起的调用堆栈,以便我们进行分析。如果您用自己的控制台流替换控制台流,那么有很多方法可以让您自责。

你的, 阿洛伊斯克劳斯

【讨论】:

我有一张照片,但由于这是我在这个网站上的第一篇文章,我不被允许发帖。 我有一张照片,但由于这是我在这个网站上的第一篇文章,我不允许发布。它显示了它挂在活动线程和调用堆栈上的行。如果您认为这有帮助,我可以发送它吗?我的程序是一个 .exe,它启动并产生两个线程来引导两组相同的硬件。但是,当仅启动一个线程(和一组硬件)时,就会看到我描述的失败。然后主线程退出(虽然我确实做了一个 t1.join 但仍然看到错误。 在这个新线程中,当我启动硬件时,我调用我的日志类来显示启动消息。这些静态日志记录方法调用一个委托,如果订阅者实现它将显示消息....目前我只是在编写一个控制台应用程序,但可能会在某个时候将其更改为 Windows 窗体....无论如何日志记录。我实现的 WriteLine() 方法由使用 Console.Writeline() 实现它的方法订阅...偶尔当它挂起并且我在 VS 中暂停时,我看到第一个生成的线程是否卡在 Console.Writeline()方法 它永远不会返回......我快速阅读了你提到的控制台重定向内容......为什么我需要这样做?是不是因为主线程不是写入控制台的线程。问候, 是的,悬挂线程的屏幕截图会有所帮助。将其发送到 gmx dot de 的 akraus1。您的控制台应用程序是否由另一个读取/重定向您的输出的进程启动?【参考方案4】:

这有点远,但我想知道您是否使用 ToString() 方法获取锁定的对象调用 Console.WriteLine。如果是这样,您可能会因 Console.WriteLine 内部获取的锁而陷入死锁。

我曾经将this bug report 发布到 Microsoft Connect,但遗憾的是他们拒绝修复它。

【讨论】:

是的,我刚刚阅读了您关于“连接”的帖子...看起来很相似...我明天在办公室时会看看代码。

以上是关于使用 Console.Writeline() 或 Console.Write() 时,多线程 C# 控制台应用程序中不经常挂起的主要内容,如果未能解决你的问题,请参考以下文章

Console.WriteLine为空

在 Console.WriteLine 上使用三元运算符

C#中的format和console.writeline有啥区别

如何使用 Console.WriteLine 对齐列中的文本?

有没有办法删除刚刚使用 Console.WriteLine 写入的字符?

如何使用 Console.WriteLine() 多次打印相同的字符 [重复]