2010-06-02 88 views
9

創建簡單多線程安全日誌類的最佳方法是什麼?這是否足夠?最初創建時如何清除日誌?簡單多線程安全日誌類

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

與其編寫自己的日誌記錄實現,確保你已經看過[log4net](http://logging.apache.org/log4net/index.html)等。如果這是一個僅限Windows的應用程序,並行性能是一個問題,請考慮[NTrace](http://ntrace.codeplex.com/)。 – hemp 2010-06-03 05:44:51

回答

11

不,您每次調用方法時都會創建一個新的鎖定對象。如果要確保一次只有一個線程可以執行該函數中的代碼,則將locker移出該函數,或者移至實例或靜態成員。如果每次寫入條目都要實例化該類,那麼locker應該是靜態的。

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

我已更新我的代碼以顯示我想如何使用日誌記錄類。 – Robert 2010-06-02 05:20:42

+0

@Robert:那麼你應該按照我寫的代碼(一個帶靜態鎖定變量的非靜態類)來獲取代碼。雖然其他地方可能有多個「Logging」類的實例,但您仍然只希望能夠一次寫入文件。 – 2010-06-02 13:11:46

5

你需要在類級別聲明同步對象:

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

可能會更好聲明你的日誌類爲static,鎖定對象@Adam羅賓遜建議。

+0

你有沒有在任何人面前看到最新的問題?大聲笑.. – VoodooChild 2010-06-02 03:58:56

+0

你將如何清除構造函數中的日誌? – Robert 2010-06-02 05:05:18

+0

@hemp:建議你對OP的問題發表評論... – 2010-06-03 06:06:24

7

使用單個監視器(鎖定)創建線程安全的日誌記錄實現不太可能產生積極的結果。雖然你可以正確地做到這一點,並且已經發布了幾個解答,但是會對性能產生顯着的負面影響,因爲每個執行日誌記錄的對象都必須與每個正在進行日誌記錄的其他對象進行同步。同時獲得多個或兩個線程完成此操作,突然間,您可能會花費更多時間等待處理。

使用單監視器方法遇到的另一個問題是,您不能保證線程將按照它們最初請求的順序獲取鎖。所以,日誌條目可能基本上不按順序出現。如果您使用這種方法進行跟蹤記錄,這可能令人沮喪。

多線程很難。輕微地接近它總會導致錯誤。

一種方法解決這個問題將是實施Producer/Consumer pattern,其中主叫方記錄器只需要寫入到內存緩衝區,並立即返回,而不是等待記錄寫入到磁盤,從而大大降低了性能損失。日誌框架將在單獨的線程上使用日誌數據並將其保留。

18

以下是使用BlockingCollection以Producer/Consumer模式(使用.Net 4)實現的日誌示例。該接口是:

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

和完整的類代碼是在這裏:

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

    } 
} 

在此示例中,用於將信息傳遞給通過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

非常好的如何解決多線程問題的例子。這應該被表決更多。 – MadTigger 2016-02-25 23:10:25