简单的MultiThread安全日志类

Rob*_*ert 10 c#

创建简单的多线程安全日志记录类的最佳方法是什么?这样的事情足够了吗?如何在最初创建日志时清除日志?

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

efd*_*mmy 24

下面是使用BlockingCollection使用Producer/Consumer模式(使用.Net 4)实现的Log的示例.界面是:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

完整的类代码在这里:

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

    }
}
Run Code Online (Sandbox Code Playgroud)

在此示例中,用于通过BlockingCollection将信息传递给写入线程的内部Param类是:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 如何解决多线程问题的一个很好的例子。这应该被更多地投票。 (2认同)

Ada*_*son 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


hem*_*emp 7

使用单个监视器(锁定)创建线程安全的日志记录实现不太可能产生积极的结果.虽然您可以正确地执行此操作,并且已经发布了几个答案,显示如何,但它会对性能产生显着的负面影响,因为每个执行日志记录的对象都必须与执行日志记录的每个其他对象同步.同时获得多于一个或两个线程执行此操作,突然间您可能会花费更多时间等待处理.

使用单一监视器方法遇到的另一个问题是,您无法保证线程将按照它们最初请求的顺序获取锁定.因此,日志条目可能基本上不按顺序出现.如果您将此用于跟踪日志记录,那可能会令人沮丧.

多线程很难.轻轻接近它总会导致错误.

解决此问题的一种方法是实现生产者/消费者模式,其中记录器的调用者只需要写入内存缓冲区并立即返回而不是等待记录器写入磁盘,从而大大降低了性能损失.日志记录框架将在一个单独的线程上使用日志数据并将其保留.

  • @Perdi Estaquel 该答案在我三年后发布,提出了特定的生产者/消费者解决方案。我还没有评估具体的实现,但我支持我的回答中所述的方法。 (2认同)

Mit*_*eat 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();
        } 
    } 
} 
Run Code Online (Sandbox Code Playgroud)

可能更好地将您的日志记录类声明为static,并且锁定对象为@Adam Robinson建议.