Atomare farbige Konsolenausgabe (thread safe)

Ich hätte mir nicht gedacht, dass ich mal über so etwas triviales wie die Konsolenausgabe posten werde, aber wieso nicht, fange ich mit etwas an, was in jedem “Hello World!”-Beispiel zu finden ist und reichere es mit einem – hoffentlich – wissenswerten Tipp zum Thema Threading an…😉

In einem meiner vielen Mockup-Projekte, in welchen ich einfach naiv drauf los programmiere um zu Lernen (jaja, so was mach ich ständig, nennt sich auch “trial and failure” oder “learning by doing”), bin ich auf folgendes Problem gestoßen:

Ich habe in meinem Programm, welches mehrere Threads instanziiert, in den einzelnen Threads Meldungen ausgegeben, was denn gerade passiert.

Nun ist das ja an sich nichts ungewöhnliches, WriteLine ist „thread safe“ und kann ohne Bedenken in mehreren parallel laufenden Threads verwendet werden, ohne dass sich die Threads in die Quere kommen.

Um in meinem Testprogramm nun Fehler besser von den „normalen“ Meldungen zu unterscheiden, habe ich diese rot eingefärbt.

Um die Konsolenausgabe einzufärben und anschließend in den Ursprungszustand wiederherzustellen ist folgender Code vonnöten:

Console.ForegroundColor = consoleColor;
Console.WriteLine("Meldung");
Console.ResetColor();

Wieso hab ich denn ein Problem damit?

Das Ganze hat nun aber einen entscheidenden Haken: der Code ist nicht atomar, das heißt, wird dieser Code von zwei Threads gleichzeitig aufgerufen, dann kann es sein, dass die Ausgabe die falsche Farbe hat!
Am besten ich erkläre es an einem Beispiel.
Folgender Code führt parallel eine Konsolenausgabe aus und weist ihr eine zufällige Farbe zu:

Random rnd = new Random();
Parallel.For(0, Console.WindowHeight - 1, i =>
{
    ConsoleColor consoleColor = (ConsoleColor)rnd.Next(7, 16);

    Console.ForegroundColor = consoleColor;
    Console.WriteLine("Meldung: {0}", consoleColor);
    Console.ResetColor();
});

Das Ergebnis kann man hier bewundern. Wie man sieht stimmen die Farben überhaupt nicht zusammen!

Die Lösung

Was man nun machen muss ist das Ganze atomar zu machen, also alle Threads während dieser Operation zu blockieren, bis die Ausgabe erledigt wurde.

Dies kann in C# mit einem Monitor.Enter oder mit dem C# Schlüsselwort lock (ist nichts anderes als ein Alias auf Monitor.Enter) erledigt werden:

lock (Console.Out)
{
    Console.ForegroundColor = consoleColor;
    Console.WriteLine("Meldung: {0}", consoleColor);
    Console.ResetColor();
}

Alles was innerhalb dieses locks passiert ist atomar, dh. keine anderen Konsolenoperationen kommen meinem Code in der Zwischenzeit in die Quere.

Tagged , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: