2015-11-25 8 views
8

Ich habe ein seltsames Problem in der Produktion mit einem Windows-Dienst zufällig hängen und würde jede Hilfe bei der Ursachenanalyse zu schätzen wissen.Console.Out und Console.Error Race Condition Fehler in einem Windows-Dienst geschrieben in .NET 4.5

Der Dienst ist in C# geschrieben und wird auf einem Computer mit .NET 4.5 bereitgestellt (obwohl ich es auch mit .NET 4.5.1 reproduzieren kann).

Der Fehler gemeldet ist:

Probable I/O race condition detected while copying memory. 
The I/O package is not thread safe by default. 
In multithreaded applications, a stream must be accessed in a thread-safe way, such as a thread-safe wrapper returned by TextReader's or TextWriter's Synchronized methods. 
This also applies to classes like StreamWriter and StreamReader. 

Ich habe die Quelle der Ausnahme Anrufe Console.WriteLine() und Console.Error.WriteLine() in einem Logger verengt. Diese werden von mehreren Threads aufgerufen, und bei hoher Auslastung wird der Fehler angezeigt und der Dienst hängt.

Allerdings, nach MSDN ist die gesamte Console-Klasse Thread-sicher (und ich habe es vorher aus mehreren Threads verwendet, keine Probleme). Außerdem wird dieses Problem nicht angezeigt, wenn Sie denselben Code wie eine Konsolenanwendung ausführen; nur von einem Windows-Dienst. Und schließlich zeigt der Stack-Trace für die Exception einen internen Aufruf von SyncTextWriter in der Konsolenklasse, der die in der Exception erwähnte synchronisierte Version sein sollte.

Weiß jemand, ob ich hier etwas falsch mache oder einen Punkt verpasse? Eine mögliche Problemumgehung scheint die Out- und Err-Streams nach/dev/null umzuleiten, aber ich würde eine detailliertere Analyse bevorzugen, die meines Wissens nach .NET nicht funktioniert.

Ich habe einen Repro Windows-Dienst erstellt, der den Fehler ausgibt, wenn es versucht wird. Code ist unten.

Service-Klasse:

[RunInstaller(true)] 
public partial class ParallelTest : ServiceBase 
{ 
    public ParallelTest() 
    { 
     InitializeComponent(); 
     this.ServiceName = "ATestService"; 
    } 

    protected override void OnStart(string[] args) 
    { 
     Thread t = new Thread(DoWork); 
     t.IsBackground = false; 

     this.EventLog.WriteEntry("Starting worker thread"); 
     t.Start(); 

     this.EventLog.WriteEntry("Starting service"); 
    } 

    protected override void OnStop() 
    { 
    } 

    private void DoWork() 
    { 
     this.EventLog.WriteEntry("Starting"); 
     Parallel.For(0, 1000, new ParallelOptions() { MaxDegreeOfParallelism = 10 }, (_) => 
     { 
      try 
      { 
       for (int i = 0; i < 10; i++) 
       { 
        Console.WriteLine("test message to the out stream"); 
        Thread.Sleep(100); 
        Console.Error.WriteLine("Test message to the error stream"); 
       } 
      } 
      catch (Exception ex) 
      { 
       this.EventLog.WriteEntry(ex.Message, EventLogEntryType.Error); 
       //throw; 
      } 
     }); 
     this.EventLog.WriteEntry("Finished"); 
    } 
} 

Hauptklasse:

static class Program 
{ 
    /// <summary> 
    /// The main entry point for the application. 
    /// </summary> 
    static void Main() 
    { 
     // Remove comment below to stop the errors 
     //Console.SetOut(new StreamWriter(Stream.Null)); 
     //Console.SetError(new StreamWriter(Stream.Null)); 

     ServiceBase[] ServicesToRun; 
     ServicesToRun = new ServiceBase[] 
     { 
      new ParallelTest() 
     }; 
     ServiceBase.Run(ServicesToRun); 
    } 
} 

Installer Klasse:

partial class ProjectInstaller 
{ 
    /// <summary> 
    /// Required designer variable. 
    /// </summary> 
    private System.ComponentModel.IContainer components = null; 

    /// <summary> 
    /// Clean up any resources being used. 
    /// </summary> 
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> 
    protected override void Dispose(bool disposing) 
    { 
     if (disposing && (components != null)) 
     { 
      components.Dispose(); 
     } 
     base.Dispose(disposing); 
    } 

    #region Component Designer generated code 

    /// <summary> 
    /// Required method for Designer support - do not modify 
    /// the contents of this method with the code editor. 
    /// </summary> 
    private void InitializeComponent() 
    { 
     this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller(); 
     this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller(); 
     // 
     // serviceProcessInstaller1 
     // 
     this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem; 
     this.serviceProcessInstaller1.Password = null; 
     this.serviceProcessInstaller1.Username = null; 
     // 
     // serviceInstaller1 
     // 
     this.serviceInstaller1.ServiceName = "ATestServiceHere"; 
     // 
     // ProjectInstaller 
     // 
     this.Installers.AddRange(new System.Configuration.Install.Installer[] { 
     this.serviceProcessInstaller1, 
     this.serviceInstaller1}); 

    } 

    #endregion 

    private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1; 
    private System.ServiceProcess.ServiceInstaller serviceInstaller1; 
} 

Installation dieses Service mit InstallUtil.exe und starten sie die Fehler in den Ereignisprotokollen Log.

+0

Blinde ohne Stack-Trace zu erraten.Ein Dienst verfügt nicht über eine Konsole. Daher ist es nicht sinnvoll, Console.Write/Line() aufzurufen. Bang, Problem gelöst. –

+0

In der Tat habe ich Console.Out und Console.Err auf den Null-Stream umgeleitet und das löst das Problem (einige Drittanbieter-Bibliotheken schreiben direkt darauf). Ich bin jedoch neugierig, ob dies ein Fehler in der Console-Klasse ist oder nicht. Eine Probe Stack-Trace (Console.WriteLine scheint inlined werden): 'System.Buffer.InternalBlockCopy (Array src, Int32 srcOffsetBytes, Array dst, Int32 dstOffsetBytes, Int32 byteCount) System.IO.StreamWriter.Write (Char [] Puffer, Int32-Index, Int32-Anzahl) System.IO.TextWriter.WriteLine (Zeichenfolgenwert) System.IO.TextWriter.SyncTextWriter.WriteLine (Zeichenfolgenwert) ' – braintechd

+0

Console.Out wird träge erstellt. Dadurch wird ein möglicher Threading-Race in der [MethodImpl (MethodImplOptions.Synchronized)] - Implementierung geöffnet, auf die SyncTextWriter.WriteLine() angewiesen ist. Der einfachste Weg, dies zu vermeiden, wäre, wenn Sie Console.Out nicht einfach neu zuweisen, eine Console.WriteLine() -Anweisung in Ihrer Main-Methode hinzuzufügen. Überlegen Sie, ob Sie diesen Fehler unter connect.microsoft.com btw ablegen möchten. –

Antwort

10

Console.Out und Console.Error sind beide Thread-sicher, da sie jeweils einen Thread-sicheren Wrapper (über TextWriter.Synchronized) für die Konsolenausgabe und den Fehlerstream TextWriters zurückgeben. Diese Thread-Sicherheit gilt jedoch nur, wenn Console.Out und Console.Error TextWriter für verschiedene Streams sind.

Der Grund, dass Ihr Code eine Ausnahme auslöst, wenn er als Windows-Dienst ausgeführt wird, ist in diesem Fall, dass Ausgabe und Fehler TextWriter auf StreamWriter.Null gesetzt sind, was ein Singleton ist. Ihr Code ruft sowohl Console.WriteLine als auch Console.Error.WriteLine auf und verursacht die Ausnahme, wenn ein Thread Console.WriteLine zur gleichen Zeit aufruft, zu der ein anderer Thread Console.Error.WriteLine aufruft. Dies bewirkt, dass derselbe Stream gleichzeitig von zwei Threads geschrieben wird, was dazu führt, dass beim Kopieren des Speichers die "Wahrscheinliche I/O-Race-Bedingung erkannt wird". Ausnahme. Wenn Sie nur Console.WriteLine oder nur Console.Error.WriteLine verwenden, stellen Sie fest, dass die Ausnahme nicht mehr auftritt.

Hier ist ein minimales Nicht-Service-Konsole Programm, das das Problem veranschaulicht:

using System; 
using System.IO; 
using System.Threading.Tasks; 

class Program 
{ 
    static void Main(string[] args) 
    { 
     var oldOut = Console.Out; 
     var oldError = Console.Error; 

     Console.SetOut(StreamWriter.Null); 
     Console.SetError(StreamWriter.Null); 
     Parallel.For(0, 2, new ParallelOptions() { MaxDegreeOfParallelism = 2 }, (_) => 
     { 
      try 
      { 
       while(true) 
       { 
        Console.WriteLine("test message to the out stream"); 
        Console.Error.WriteLine("Test message to the error stream"); 
       } 
      } 
      catch (Exception ex) 
      { 
       Console.SetOut(oldOut); 
       Console.SetError(oldError); 
       Console.WriteLine(ex); 
       Environment.Exit(1); 
      } 
     }); 
    } 
} 
Verwandte Themen