使用Console.Writeline()或Console.Write()时,多线程C#控制台应用程序偶尔会挂起

11
我已经编写了一个控制台应用程序,使用console.write和console.writeline提供一些日志记录。该应用程序是一个服务器应用程序,使用异步beginacceptconnection()和beginread()(套接字)进行通信。偶尔会收到它挂起的报告,从我所能做的有限调试中,我能够看到问题是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 是线程安全的,因此我实际上不需要在其周围加锁... 我也无法相信 Microsoft 的代码是错的(;-),所以我猜想这是我的代码与其他部分交互引起的错误。
现在我的问题是:我应该做些什么来防止 Console.WriteLine 和 Console.Write 被中断?我猜测可能是有些东西中断了它们,但我真的不确定!
非常感谢任何帮助。
问候,
戈登。

明天我回到办公室后会发布一些内容。 - Gordon Roxburgh
1
只需按下Ctrl+S,立即卡死。 - Hans Passant
看了代码后,有很多(真的很多)可以删除的内容。我没有发现明显的错误,但Begin/EndCrtiticalRegion不是必要的,也不需要用于Console.WriteLine()。你的错误可能在套接字代码中。 - H H
通过阅读mostafa的答案并在读取后使用ReadLine解决了类似的问题。可能与https://social.msdn.microsoft.com/Forums/vstudio/en-US/3a168caa-6aca-428d-b219-b0b8cfba6270/blocking-on-processstart-with-net-45?forum=clr有关。 - Jure Špik
有一个KB可以解决这个问题,但是WU在我的Win 8.1上找不到它:https://support.microsoft.com/en-us/kb/2805221(CLR问题#4) - Jure Špik
显示剩余3条评论
4个回答

8

我也遇到了同样的问题。

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

之后,我将其替换为一个无限循环,我的问题就得到了解决。


1
这对我有用...将ReadKey更改为ReadLine,问题就解决了。谢谢 - Greg Ennis

2
你应该通过移除日志记录周围的锁来解决问题。日志记录是通过Console.WriteLine进行的,它是同步的(并且线程安全的)。你可能会通过自己的锁机制导致死锁(尽管我没有看到代码无法验证)。

同意,但它并没有解释为什么一旦调用console.writeline或console.write方法就无法返回(从而创建您提到的死锁)......我只是在VS旁边得到一个绿色箭头,并显示一条消息,指出该方法已被调用但未返回......如果我取消暂停vs并等待30秒....然后再次暂停,它仍然停留在同一位置。敬礼, - Gordon Roxburgh
死锁不是由于 Console.WriteLine 无法返回而引起的(这只是阻塞)。死锁发生在线程争夺资源时。由于您没有发布任何代码,我无法告诉您死锁发生在哪里。 - Ethan Cabiac
控制台.writeline()如何阻塞呢?代码现已发布。 - Gordon Roxburgh

2
我猜您的应用程序是由另一个进程启动的,该进程重定向了stderr和stdout。如果您的“观察器”进程在同一线程上使用ReadToEnd()读取两个流,可能会发生死锁。
死锁的另一个选项是通过stdin发送子进程输入,该子进程又启动另一个具有无限等待输入控制台的进程。这曾经发生在我身上,当时我使用wmic.exe时stdin被重定向时就会阻塞。
如果您已经修改了日志类,我怀疑您更改了Console.Out的基础流。请至少发布应用程序挂起的调用堆栈,以便我们进行分析。如果您用自己的流替换掉控制台流,可能会有很多自杀式行为。
谨此, Alois Kraus

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

2
这有点冒险,但我想知道你是否使用了其 ToString() 方法需要锁定的对象来调用 Console.WriteLine。如果是这样,您可能会因为 Console.WriteLine 内部获取的锁而陷入死锁状态。
我曾经在 Microsoft Connect 上发布过 这个错误报告,但遗憾的是他们拒绝修复它。

是的,我刚刚看了你在“connect”上的帖子...看起来很相似...我明天上班时会查看代码。 - Gordon Roxburgh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接