多线程应用中控制台颜色更改无法正常工作

15
我正在开发一个多线程应用程序(服务器),它基本上是一个控制台应用程序。在其中,我显示处理日志到控制台,默认情况下为白色。 但是,在成功的交易中,我会将文本显示为绿色,而在失败的交易中,则会将文本显示为红色。因此,我在Program.cs中有三个单独的函数来实现这一点。
对于简单的日志:
public static void Write(string text)
{
        try
        {
            Console.Out.Write(text);
        }
        catch (Exception)
        { }
    }

对于未成功的交易,我会将颜色更改为红色,然后打印出来,然后再改回白色。

    public static void WriteError(string text)
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine("\t" + text);
        Console.ForegroundColor = ConsoleColor.White;
    }

为了成功的交易,我将颜色更改为绿色,然后打印,最后再改回白色。

    public static void WriteSuccess(string text)
    {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine("\t" + text);
        Console.ForegroundColor = ConsoleColor.White;

    }

问题在于当超过200个客户端连接时,每个客户端的交易日志都会打印在控制台上。而且当我将单行颜色更改为绿色时,许多其他正常日志行也会变成绿色。

请告诉我如何解决这个问题。


我认为你可能需要锁定。 - AMember
4个回答

23
如果你的类是唯一一个向控制台写入内容的类,那么像其他人提到的那样,在单个私有对象上锁定将起作用。
但是,如果有其他写入者,您还需要与他们同步,否则他们可能会在您进入锁定时写入。如果您查看Console的实现,您会注意到Console.OutSyncTextWriter,它使用自身(使用[MethodImplAttribute(MethodImplOptions.Synchronized)])进行同步。这意味着,如果您将其用作同步对象,则将与其他编写器同步:
lock (Console.Out)
{
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine("\t" + text);
    Console.ForegroundColor = ConsoleColor.White;
}

如果其他写入程序也设置颜色并使用自己的同步对象,则此方法仍无法正确工作。但如果其他写入程序也对Console.Out进行同步,那么它将起作用。

这种方法的问题在于它依赖于实现细节,但我不知道是否有更好的解决方案。


我试图通过在控制台输出中为线程设置不同的颜色来说明线程的工作原理。然而,我并不完全理解为什么线程之间的颜色会混合,而您的建议可以防止这种情况发生。显然,线程并没有相互交叉,因为预期的输出值和相应的线程名称都已打印出来。然而,根据颜色混合的初始想法是它们确实交叉了。 - WhoAmI

12
这就是你的问题所在:
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine("\t" + text);

您设置了颜色,另一个线程中断,将颜色设置为其他值,然后您打印文本(颜色不正确)。

您需要使用锁来保护该部分:

 static object lockObj = new object();

 public static void WriteError(string text)
 {
    lock(lockObj)
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine("\t" + text);
        Console.ForegroundColor = ConsoleColor.White;
    }
 }

你需要为 WriteSuccess(...) 做同样的事情。

2
让线程直接在控制台中写入是一个好主意吗? - adripanico
它不起作用。我使用了您的建议,并使用了静态对象lockObj = new object(); - M Naeem Ashraf
3
WriteLine 可以保证线程安全,但 ForegroundColor 加上 WriteLine 再加上 ForegroundColor 就不是了。无法确保在这三个方法完成之前,没有其他线程被操作系统调度选中。 - adripanico
是的,我只有两个函数可以更改颜色,它们在同一个类中。我在该类中声明了lockObj,并在我更改颜色的两个函数中锁定了该对象。但问题仍未解决。 - M Naeem Ashraf
@adirpanico 我在 Program.cs 中声明了该对象。我有这两个函数。 - M Naeem Ashraf
显示剩余7条评论

1

通过使用锁语句,使您的有色写入变为原子操作:

// Only one thread can enter this section at a time
lock(_lockObj) 
{
   Console.ForegroundColor = ConsoleColor.Green;
   Console.WriteLine("\t" + text);
   Console.ForegroundColor = ConsoleColor.White;
}

_lockObj 应该被声明为你的类的私有静态成员:

private static Object _lockObj = new Object();

这正是@pivotnig的建议。 - adripanico
从您提供的代码来看,您需要在Write方法中实现锁定,否则它将与另外两个更改颜色的方法竞争: public static void Write(string text) { lock(_lockObj) { Console.WriteLine(text); } } - jlew

0
你应该使用 Mutex 或监视器(lock 在某个对象上更容易)来同步访问控制台。

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