2010-06-02 13 views
9

¿Cuál es el mejor enfoque para crear una clase simple de registro seguro de subprocesos múltiples? Es algo como esto suficiente? ¿Cómo purgaría el registro cuando se creó inicialmente?Clase de registro seguro de múltiples hilos simple

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

En lugar de escribir su propia implementación de registro, asegúrese de haber echado un vistazo a [log4net] (http://logging.apache.org/log4net/index.html) et al. Si esta es una aplicación solo para Windows y el rendimiento en paralelo es una preocupación, también considere [NTrace] (http://ntrace.codeplex.com/). – hemp

Respuesta

11

No, está creando un nuevo objeto de bloqueo cada vez que se llama al método. Si desea asegurarse de que solo un hilo a la vez pueda ejecutar el código en esa función, mueva locker fuera de la función, ya sea a una instancia o un miembro estático. Si se crea una instancia de esta clase cada vez que se escribe una entrada, entonces locker probablemente sea estático.

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

He actualizado mi código para mostrar cómo quiero usar la clase de registro. – Robert

+0

@Robert: Entonces deberías tomar el código tal como lo escribí (una clase no estática con una variable de bloqueo estático). Si bien podría tener varias instancias de la clase 'Logging' en otro lugar, solo desea que una sea capaz de escribir en el archivo a la vez. –

5

tiene que declarar el objeto de sincronización a nivel de clase:

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(); 
     } 
    } 
} 

podría ser mejor declarar una clase como la tala static, y el objeto de bloqueo como @ Adam Robinson sugeridos.

+0

¿Ves la pregunta más nueva antes que nadie? lol .. – VoodooChild

+0

¿Cómo purgaría el registro en el constructor? – Robert

+0

@hemp: le sugiero que publique un comentario a la pregunta de OP ... –

7

La creación de una implementación de registro segura de subprocesos con un solo monitor (bloqueo) es poco probable que arroje resultados positivos. Si bien puede hacer esto correctamente, y se han publicado varias respuestas que muestran cómo, tendría un efecto negativo dramático en el rendimiento ya que cada objeto que realiza el registro tendría que sincronizarse con todos los demás objetos que realizan el registro. Obtenga más de uno o dos hilos haciendo esto al mismo tiempo y de repente puede pasar más tiempo esperando que procesando.

El otro problema con el que se topa con el enfoque de monitor único es que no tiene ninguna garantía de que los hilos adquieran el bloqueo en el orden en que lo solicitaron inicialmente. Por lo tanto, las entradas de registro pueden aparecer esencialmente desordenadas. Eso puede ser frustrante si está usando esto para el registro de rastreo.

Multi-threading es difícil. Acercarse a ella a la ligera siempre dará lugar a errores.

Un enfoque a este problema sería la implementación de la Producer/Consumer pattern, en el que las personas que llaman al registrador sólo tienen que escribir en un búfer de memoria y volver inmediatamente en vez de esperar a que el registrador a escribir en el disco, lo que reduce drásticamente la penalización de rendimiento . El marco de trabajo de registro, en un hilo separado, consumiría los datos de registro y lo conservaría.

18

Aquí hay una muestra de un Log implementado con el patrón Producer/Consumer (con .Net 4) usando un BlockingCollection. La interfaz es:

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); 
    } 
} 

y el código de clase completo está aquí:

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(); 
     } 

    } 
} 

En esta muestra, la clase Param interno utilizado para pasar información a la rosca de escritura a través de la BlockingCollection es:

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

Muy buen ejemplo de cómo resolver los problemas de subprocesos múltiples. Esto debería votarse más. – MadTigger

Cuestiones relacionadas