2010-06-02 8 views
9

Qual è l'approccio migliore per creare una classe di registrazione sicura multithread semplice? È sufficiente qualcosa del genere? Come posso eliminare il registro quando viene inizialmente creato?Classe di registro sicuro MultiThread semplice

public class Logging 
{ 
    public Logging() 
    { 
    } 

    public void WriteToLog(string message) 
    { 
     object locker = new object(); 

     lock(locker) 
     { 
      StreamWriter SW; 
      SW=File.AppendText("Data\\Log.txt"); 
      SW.WriteLine(message); 
      SW.Close(); 
     } 
    } 
} 

public partial class MainWindow : Window 
{ 
    public static MainWindow Instance { get; private set; } 
    public Logging Log { get; set; } 

    public MainWindow() 
    { 
     Instance = this; 
     Log = new Logging(); 
    } 
} 
+0

Piuttosto che scrivere la propria implementazione di registrazione, assicurarsi di aver dato un'occhiata a [log4net] (http://logging.apache.org/log4net/index.html) et al. Se questa è un'app solo per Windows e le prestazioni parallele sono un problema, considera anche [NTrace] (http://ntrace.codeplex.com/). – hemp

risposta

11

No, stai creando un nuovo oggetto di blocco ogni volta che viene chiamato il metodo. Se vuoi assicurarti che solo un thread alla volta possa eseguire il codice in quella funzione, allora sposta locker fuori dalla funzione, su un'istanza o su un membro statico. Se questa classe viene istanziata ogni volta che si deve scrivere una voce, allora locker dovrebbe essere statico.

public class Logging 
{ 
    public Logging() 
    { 
    } 

    private static readonly object locker = new object(); 

    public void WriteToLog(string message) 
    { 
     lock(locker) 
     { 
      StreamWriter SW; 
      SW=File.AppendText("Data\\Log.txt"); 
      SW.WriteLine(message); 
      SW.Close(); 
     } 
    } 
} 
+0

Ho aggiornato il mio codice per mostrare come voglio usare la classe di registrazione. – Robert

+0

@Robert: Quindi dovresti prendere il codice come l'ho scritto (una classe non statica con una variabile di blocco statica). Mentre potresti avere più istanze della classe 'Logging' altrove, vuoi comunque che una sola sia in grado di scrivere sul file alla volta. –

5

è necessario dichiarare l'oggetto di sincronizzazione a livello di classe:

public class Logging 
{ 
    private static readonly object locker = new object(); 

    public Logging() 
    { 
    } 

    public void WriteToLog(string message) 
    { 
     lock(locker) 
     { 
      StreamWriter SW; 
      SW=File.AppendText("Data\\Log.txt"); 
      SW.WriteLine(message); 
      SW.Close(); 

      SW.Dispose(); 
     } 
    } 
} 

potrebbe essere migliore di dichiarare la classe di registrazione come static, e l'oggetto di blocco come @ Adamo Robinson suggerito.

+0

Vedi la domanda più recente prima di chiunque? lol .. – VoodooChild

+0

Come si eliminerebbe il registro nel costruttore? – Robert

+0

@hemp: suggerisci di postare un commento alla domanda dell'OP ... –

7

È improbabile che la creazione di un'implementazione di registrazione thread-safe utilizzando un singolo monitor (blocco) produca risultati positivi. Anche se è possibile farlo correttamente e sono state pubblicate diverse risposte che mostrano come, avrebbe un drammatico effetto negativo sulle prestazioni poiché ogni oggetto che esegue la registrazione dovrebbe sincronizzarsi con ogni altro oggetto che esegue la registrazione. Ottieni più di uno o due thread per farlo allo stesso tempo e all'improvviso potresti trascorrere più tempo in attesa che nell'elaborazione.

L'altro problema che si incontra con l'approccio a monitor singolo è che non si ha alcuna garanzia che i thread acquisiscano il blocco nell'ordine in cui inizialmente lo richiedevano. Pertanto, le voci del registro potrebbero apparire essenzialmente fuori ordine. Questo può essere frustrante se lo stai usando per la registrazione di traccia.

Il multi-threading è difficile. Avvicinarsi leggermente porterà sempre a degli insetti.

Una soluzione a questo problema sarebbe quella di attuare il Producer/Consumer pattern, in cui i chiamanti allo strumento solo bisogno di scrivere in un buffer di memoria e ritornare immediatamente invece di attendere che il registratore di scrivere su disco, riducendo drasticamente la pena di prestazioni . Il framework di registrazione, in un thread separato, consuma i dati del registro e lo mantiene.

18

Ecco un esempio per un registro implementato con il pattern Producer/Consumer (con .Net 4) utilizzando BlockingCollection. L'interfaccia è:

namespace Log 
{ 
    public interface ILogger 
    { 
     void WriteLine(string msg); 
     void WriteError(string errorMsg); 
     void WriteError(string errorObject, string errorAction, string errorMsg); 
     void WriteWarning(string errorObject, string errorAction, string errorMsg); 
    } 
} 

e il codice di classe completo è qui:

using System; 
using System.Collections.Concurrent; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

namespace Log 
{ 
    // Reentrant Logger written with Producer/Consumer pattern. 
    // It creates a thread that receives write commands through a Queue (a BlockingCollection). 
    // The user of this log has just to call Logger.WriteLine() and the log is transparently written asynchronously. 

    public class Logger : ILogger 
    { 
     BlockingCollection<Param> bc = new BlockingCollection<Param>(); 

     // Constructor create the thread that wait for work on .GetConsumingEnumerable() 
     public Logger() 
     { 
      Task.Factory.StartNew(() => 
        { 
         foreach (Param p in bc.GetConsumingEnumerable()) 
         { 
          switch (p.Ltype) 
          { 
           case Log.Param.LogType.Info: 
            const string LINE_MSG = "[{0}] {1}"; 
            Console.WriteLine(String.Format(LINE_MSG, LogTimeStamp(), p.Msg)); 
            break; 
           case Log.Param.LogType.Warning: 
            const string WARNING_MSG = "[{3}] * Warning {0} (Action {1} on {2})"; 
            Console.WriteLine(String.Format(WARNING_MSG, p.Msg, p.Action, p.Obj, LogTimeStamp())); 
            break; 
           case Log.Param.LogType.Error: 
            const string ERROR_MSG = "[{3}] *** Error {0} (Action {1} on {2})"; 
            Console.WriteLine(String.Format(ERROR_MSG, p.Msg, p.Action, p.Obj, LogTimeStamp())); 
            break; 
           case Log.Param.LogType.SimpleError: 
            const string ERROR_MSG_SIMPLE = "[{0}] *** Error {1}"; 
            Console.WriteLine(String.Format(ERROR_MSG_SIMPLE, LogTimeStamp(), p.Msg)); 
            break; 
           default: 
            Console.WriteLine(String.Format(LINE_MSG, LogTimeStamp(), p.Msg)); 
            break; 
          } 
         } 
        }); 
     } 

     ~Logger() 
     { 
      // Free the writing thread 
      bc.CompleteAdding(); 
     } 

     // Just call this method to log something (it will return quickly because it just queue the work with bc.Add(p)) 
     public void WriteLine(string msg) 
     { 
      Param p = new Param(Log.Param.LogType.Info, msg); 
      bc.Add(p); 
     } 

     public void WriteError(string errorMsg) 
     { 
      Param p = new Param(Log.Param.LogType.SimpleError, errorMsg); 
      bc.Add(p); 
     } 

     public void WriteError(string errorObject, string errorAction, string errorMsg) 
     { 
      Param p = new Param(Log.Param.LogType.Error, errorMsg, errorAction, errorObject); 
      bc.Add(p); 
     } 

     public void WriteWarning(string errorObject, string errorAction, string errorMsg) 
     { 
      Param p = new Param(Log.Param.LogType.Warning, errorMsg, errorAction, errorObject); 
      bc.Add(p); 
     } 

     string LogTimeStamp() 
     { 
      DateTime now = DateTime.Now; 
      return now.ToShortTimeString(); 
     } 

    } 
} 

In questo esempio, la classe Param interno utilizzato per trasmettere informazioni al filo scrittura attraverso il BlockingCollection è:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace Log 
{ 
    internal class Param 
    { 
     internal enum LogType { Info, Warning, Error, SimpleError }; 

     internal LogType Ltype { get; set; } // Type of log 
     internal string Msg { get; set; }  // Message 
     internal string Action { get; set; } // Action when error or warning occurs (optional) 
     internal string Obj { get; set; }  // Object that was processed whend error or warning occurs (optional) 

     internal Param() 
     { 
      Ltype = LogType.Info; 
      Msg = ""; 
     } 
     internal Param(LogType logType, string logMsg) 
     { 
      Ltype = logType; 
      Msg = logMsg; 
     } 
     internal Param(LogType logType, string logMsg, string logAction, string logObj) 
     { 
      Ltype = logType; 
      Msg = logMsg; 
      Action = logAction; 
      Obj = logObj; 
     } 
    } 
} 
+1

Un bell'esempio di come risolvere i problemi multi-thread. Questo dovrebbe essere votato di più. – MadTigger