2014-05-07 4 views
14

Stringbuilder asynchron in Datei schreiben. Dieser Code übernimmt die Kontrolle über eine Datei, schreibt einen Stream und gibt ihn frei. Es behandelt Anfragen von asynchronen Operationen, die jederzeit eingehen können.In eine Thread-sichere Weise in die Datei schreiben

Der FilePath wird pro Klasseninstanzen festgelegt (das Sperrobjekt ist also pro Instanz), aber es besteht ein Konfliktpotential, da diese Klassen FilePaths gemeinsam nutzen können. Diese Art von Konflikt sowie alle anderen Typen außerhalb der Klasseninstanz werden mit Wiederholungen behandelt.

Ist dieser Code für seinen Zweck geeignet? Gibt es einen besseren Weg, damit umzugehen, was bedeutet, dass man sich weniger auf die Fang- und Wiederholungsmechanik verlässt?

Wie vermeide ich auch das Abfangen von Ausnahmen, die aus anderen Gründen aufgetreten sind.

public string Filepath { get; set; } 
private Object locker = new Object(); 

public async Task WriteToFile(StringBuilder text) 
    { 
     int timeOut = 100; 
     Stopwatch stopwatch = new Stopwatch(); 
     stopwatch.Start(); 
     while (true) 
     { 
      try 
      { 
       //Wait for resource to be free 
       lock (locker) 
       { 
        using (FileStream file = new FileStream(Filepath, FileMode.Append, FileAccess.Write, FileShare.Read)) 
        using (StreamWriter writer = new StreamWriter(file, Encoding.Unicode)) 
        { 
         writer.Write(text.ToString()); 
        } 
       } 
       break; 
      } 
      catch 
      { 
       //File not available, conflict with other class instances or application 
      } 
      if (stopwatch.ElapsedMilliseconds > timeOut) 
      { 
       //Give up. 
       break; 
      } 
      //Wait and Retry 
      await Task.Delay(5); 
     } 
     stopwatch.Stop(); 
    } 
+0

Ihre tatsächlichen Dateischreibvorgänge sind nicht asynchron. Meintest du damit? –

+0

@StephenCleary, ja es gab anfangs aber man sollte nicht auf Dinge in einem Schloss warten, also musste ich es ändern. Ich bin nicht sicher, in welchem ​​Ausmaß das alles untergräbt. –

+0

Das klingt wie es auf codereview sein sollte tbh –

Antwort

27

Wie Sie dies erreichen, hängt sehr davon ab, wie oft Sie schreiben. Wenn Sie relativ wenig Text relativ selten schreiben, verwenden Sie einfach eine statische Sperre und seien Sie damit fertig. Das ist in jedem Fall die beste Lösung, da das Laufwerk nur eine Anfrage gleichzeitig erfüllen kann. Wenn Sie davon ausgehen, dass sich alle Ausgabedateien auf demselben Laufwerk befinden (vielleicht keine faire Annahme, aber Sie müssen mit mir rechnen), gibt es keinen großen Unterschied zwischen dem Sperren auf Anwendungsebene und dem Sperren auf Betriebssystemebene.

Also, wenn Sie erklären locker als:

static object locker = new object(); 

Sie sicher sein, werden feststellen, dass es keine Konflikte mit anderen Threads in Ihrem Programm sind.

Wenn Sie wollen, dass diese Sache kugelsicher (oder zumindest einigermaßen) ist, können Sie keine Ausnahmen abfangen. Schlechte Dinge können passieren. Sie müssen Ausnahmen in irgendeiner Weise behandeln. Was Sie angesichts von Fehlern tun, ist etwas ganz anderes. Wahrscheinlich möchten Sie es ein paar Mal wiederholen, wenn die Datei gesperrt ist. Wenn Sie einen fehlerhaften Pfad oder Dateinamenfehler oder eine vollständige Festplatte oder einen anderen Fehler erhalten, möchten Sie das Programm wahrscheinlich beenden. Auch das liegt an dir. Aber Sie können die Ausnahmebehandlung nicht vermeiden, es sei denn, das Programm stürzt bei einem Fehler ab.

By the way, können Sie den gesamten Code ersetzen:

   using (FileStream file = new FileStream(Filepath, FileMode.Append, FileAccess.Write, FileShare.Read)) 
       using (StreamWriter writer = new StreamWriter(file, Encoding.Unicode)) 
       { 
        writer.Write(text.ToString()); 
       } 

Mit einem einzigen Anruf:

File.AppendAllText(Filepath, text.ToString()); 

Sie verwenden .NET 4.0 oder höher Unter der Annahme. Siehe File.AppendAllText.

Eine andere Möglichkeit, die Sie damit umgehen können, besteht darin, dass die Threads ihre Nachrichten in eine Warteschlange schreiben und über einen dedizierten Thread verfügen, der diese Warteschlange bedient. Sie haben eine BlockingCollection von Nachrichten und zugeordneten Dateipfaden.Zum Beispiel:

class LogMessage 
{ 
    public string Filepath { get; set; } 
    public string Text { get; set; } 
} 

BlockingCollection<LogMessage> _logMessages = new BlockingCollection<LogMessage>(); 

Ihre Themen schreiben Daten in dieser Warteschlange:

_logMessages.Add(new LogMessage("foo.log", "this is a test")); 

starten Sie eine lang andauernde Hintergrundaufgabe, die nichts als Dienst tut, die Warteschlange:

foreach (var msg in _logMessages.GetConsumingEnumerable()) 
{ 
    // of course you'll want your exception handling in here 
    File.AppendAllText(msg.Filepath, msg.Text); 
} 

Ihr Potenzial Das Risiko besteht hier darin, dass Threads Nachrichten zu schnell erstellen, wodurch die Warteschlange ohne Bindung wächst, weil der Verbraucher nicht mithalten kann. Ob das in Ihrer Anwendung ein echtes Risiko darstellt, können Sie nur sagen. Wenn Sie glauben, dass dies ein Risiko darstellt, können Sie eine maximale Größe (Anzahl der Einträge) in die Warteschlange eingeben. Wenn die Warteschlangengröße diesen Wert überschreitet, warten die Hersteller, bis Platz in der Warteschlange ist, bevor sie hinzugefügt werden können.

+0

Danke, sehr durch. Teil des Grundes, warum ich meinen großen using Block mag, ist, dass ich FileShare.Read einstellen kann, was ich nicht sicher bin, ist mit File.AppendAllText der Fall. Aber ich nehme an, das ist nur ein Faktor, wenn ich mit anderen Prozessen konkurriere. –

11

Sie auch ReaderWriterLock verwenden könnten, wird es als mehr ‚angemessen‘ Weg, um Thread-Sicherheit zu steuern, wenn sie mit Lese-Schreib-Operationen zu tun ...

Zu meinen Web-Anwendungen zu debuggen (bei Remote-Debug-Ausfall) Ich benutze following ('debug.txt' landet im \ bin Ordner auf dem Server):

public static class LoggingExtensions 
{ 
    static ReaderWriterLock locker = new ReaderWriterLock(); 
    public static void WriteDebug(string text) 
    { 
     try 
     { 
      locker.AcquireWriterLock(int.MaxValue); 
      System.IO.File.AppendAllLines(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase).Replace("file:\\", ""), "debug.txt"), new[] { text }); 
     } 
     finally 
     { 
      locker.ReleaseWriterLock(); 
     } 
    } 
} 

Hoffe das spart Ihnen etwas Zeit.

+0

Gerade kam ich über diesen Beitrag, und es half mir, danke @Matas! Ich dachte, ich würde aber mitteilen, dass es einen ReaderWriterLock-Nachfolger namens ReaderWriterLockSlim gibt, siehe https://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim(v=vs.110).aspx – KBoek

+0

ReaderWriterLock (und ReaderWriterLockSlim) sind für Situationen, in denen Sie mehrere Leser und einen Schreiber zulassen müssen. In diesem Fall, wenn Sie nur aus mehreren Threads in eine Datei schreiben, aber nicht lesen, schlage ich vor, nur zu sperren. – Jamie