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.
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. –
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
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. –