实现非阻塞线程安全单例记录类

时间:2017-07-01 02:30:54

标签: c# asp.net multithreading thread-safety nonblocking

我想编写一个记录器类,它根据用户操作将输出写入日志文件。我为此编写了一个logmessage类。以下是我所拥有的。

public class LogMessage
{
    private  static Object padlock = new Object();
    private static string serviceDirectory ="C:\\MySite\\";
    private static string filePath = "\\Logs\\Combined\\ActivityMonitorLog-" + DateTime.Now.ToString("dd'-'MM'-'yyyy") + ".log";

    private static volatile LogMessage instance = new LogMessage();
    public static LogMessage Instance
    {
        get
        {
            if (instance == null)
            {
                lock (padlock)
                {
                    if (instance == null)
                        instance = new LogMessage();
                }
            }

            return instance;
        }
    }


    public void SaveLogMessage(string userName, string message, string stackTrace)
    {
        lock (padlock)
        {
            using (StreamWriter logWriter =
                new StreamWriter(serviceDirectory + filePath, true))
            {
                string logMessage = "";
                if (!string.IsNullOrEmpty(stackTrace))
                    logMessage = string.Format("{0} >> user : {1} ::: {2} ::: stack trace ::: {3}", DateTime.Now.ToString(), userName, message,stackTrace);
                else
                    logMessage = string.Format("{0} >> user : {1} ::: {2}", DateTime.Now.ToString(), userName, message);

                logWriter.WriteLine(logMessage);

            }
        }
    }
}

当我想要做一个日志条目时,我正在调用下面的方法

LogMessage.Instance.SaveLogMessage("My User", "Enter Logout PageLoad : Current Method :" + "Page_Load @ Logout.aspx.cs", "");

我相信我已经实现了单件部件和线程安全性。但我不认为这是非阻塞的,因为多个用户同时登录,每个用户都必须等到文件可用才能被写入。我在考虑使用backgroundworker从LogMessage单例对象调用此SaveLogMessage()。这是正确的做法吗?

更新

我使用backgroundworker实现了这个,完整的类和答案中提到了如何调用。感谢@bret和@huan的投入

3 个答案:

答案 0 :(得分:1)

对于你的"实例"领域,使用" readonly"而不是" volatile"因为该字段的值永远不会改变。

您甚至不需要公共实例属性 - 您可以将SaveLogMessage转换为静态方法并直接使用它。

在SaveLogMessage主体中,您可以使用Task.Run(()=> {...})在后台线程上执行日志记录操作以使其无阻塞。如果您经常进行日志记录,这可能效率不高。

请考虑使用System.Diagnostics.Trace,这样可以提供更大的灵活性。

答案 1 :(得分:1)

  1. 最好在尝试写入时使用ReaderWriterLockslim添加写锁定。
  2. 我认为使用ReaderWriterLockslim的静态方法已经足够了。 有一篇好文章here
  3. 如果你想在这里使用单身,你可以使用Lazy参考Implementing the Singleton Pattern in C#
  4. btw为什么不使用log4net或任何其他现有的库。

答案 2 :(得分:1)

最后,我设法使用后台工作程序实现了非阻塞,线程安全的单例日志记录类。我正在提交我的解决方案,以防有人发现它有用。

using System;
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Threading;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Diagnostics;
using System.Web.Configuration;
/// <summary>
/// Summary description for LogMessage
/// </summary>
public class LogMessage
{
    static ReaderWriterLock locker = new ReaderWriterLock();

    private static string serviceDirectory = HttpContext.Current != null ?
        AppDomain.CurrentDomain.BaseDirectory :
        Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
    private static string fullpath = serviceDirectory + "\\ActivityLog.log";
    private static readonly LogMessage instance = new LogMessage();

    public static LogMessage Instance
    {
        get { return instance; }
    }

    public void SaveLogMessage(string userName, string message, string stackTrace, bool inOut)
    {
        bool EnableActivityLogging = false;

        if (string.IsNullOrEmpty(WebConfigurationManager.AppSettings["EnableActivityLogging"]))
            return;

        EnableActivityLogging = Convert.ToBoolean(WebConfigurationManager.AppSettings["EnableActivityLogging"]);

        if (!EnableActivityLogging)
            return;

        BackgroundWorker logbw = new BackgroundWorker();
        logbw.DoWork += logbw_DoWork;
        logbw.RunWorkerCompleted += logbw_RunWorkerCompleted;

        List<string> paramList = new List<string>();
        paramList.Add(userName);
        paramList.Add(message);
        paramList.Add(stackTrace);
        paramList.Add(inOut.ToString());

        if (!logbw.IsBusy)
        {
            logbw.RunWorkerAsync(paramList);
        }
    }

    void logbw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        Debug.Write("Log Message Background Worker is now free...");
    }

    void logbw_DoWork(object sender, DoWorkEventArgs e)
    {
        List<string> paramList = (List<string>)e.Argument;
        string userName = paramList[0].ToString();
        string message = paramList[1].ToString();
        string stackTrace = paramList[2].ToString();
        bool inOut = bool.Parse(paramList[3].ToString());

        try
        {
            locker.AcquireWriterLock(int.MaxValue);
            using (StreamWriter logWriter =
                new StreamWriter(fullpath, true))
            {
                string logMessage = "";
                if (!string.IsNullOrEmpty(stackTrace))
                {
                    if (inOut)//IN
                    {
                        logMessage = string.Format("{0} U:{1} IN:{2} E:{3}", DateTime.Now.ToString(), userName, message, stackTrace);
                    }
                    else//OUT
                    {
                        logMessage = string.Format("{0} U:{1} OUT:{2} E:{3}", DateTime.Now.ToString(), userName, message, stackTrace);
                    }
                }
                else
                {
                    if (inOut)//IN
                    {
                        logMessage = string.Format("{0} U:{1} IN:{2}", DateTime.Now.ToString(), userName, message);
                    }
                    else//OUT
                    {
                        logMessage = string.Format("{0} U:{1} OUT:{2}", DateTime.Now.ToString(), userName, message);
                    }
                }

                logWriter.WriteLine(logMessage);

            }
        }
        finally
        {
            locker.ReleaseWriterLock();
        }
    }

}

现在我可以像下面这样使用这个单例对象来保存日志条目

    LogMessage.Instance.SaveLogMessage(Context.User.Identity.Name, "Custom Message" + " M:" + MethodInfo.GetCurrentMethod().Name + "@" + Path.GetFileName(Page.AppRelativeVirtualPath), "", true);