C# 如何有效地异步记录日志?

C# 如何有效地异步记录日志?,c#,multithreading,logging,enterprise-library,C#,Multithreading,Logging,Enterprise Library,我在我的一个项目上使用EnterpriseLibrary4进行日志记录(以及其他用途)。我注意到,我正在做的日志记录有一些成本,我可以通过在单独的线程上进行日志记录来减轻这些成本 我现在的做法是创建一个LogEntry对象,然后在一个调用Logger.Write的委托上调用BeginInvoke new Action<LogEntry>(Logger.Write).BeginInvoke(le, null, null); 新操作(Logger.Write).BeginInvoke(

我在我的一个项目上使用EnterpriseLibrary4进行日志记录(以及其他用途)。我注意到,我正在做的日志记录有一些成本,我可以通过在单独的线程上进行日志记录来减轻这些成本

我现在的做法是创建一个LogEntry对象,然后在一个调用Logger.Write的委托上调用BeginInvoke

new Action<LogEntry>(Logger.Write).BeginInvoke(le, null, null);
新操作(Logger.Write).BeginInvoke(le,null,null);
我真正想做的是将日志消息添加到队列中,然后让一个线程将LogEntry实例从队列中拉出并执行日志操作。这样做的好处是,日志记录不会干扰正在执行的操作,并且不是每个日志记录操作都会导致在线程池中抛出作业

如何以线程安全的方式创建支持多个写入程序和一个读取器的共享队列?如果队列实现的一些示例被设计为支持多个写入程序(而不导致同步/阻塞)和一个读卡器,我们将不胜感激


关于替代方法的建议也将不胜感激,尽管我对更改日志框架不感兴趣。

额外的间接级别可能会有所帮助


您的第一个异步方法调用可以将消息放入一个同步队列并设置一个事件——因此锁定发生在线程池中,而不是工作线程上——然后在引发事件时,另一个线程将消息从队列中拉出。

如果您在单独的线程上记录了某些内容,如果应用程序崩溃,消息可能不会被写入,这使得它毫无用处


原因是为什么每次写入条目后都要刷新。

是的,您需要一个生产者/消费者队列。在我的线程教程中,我有一个这样的例子——如果你查看我的页面,你会在下半部分找到代码

当然,网上还有很多其他的例子,.NET4.0也将在框架中附带一个(比我的功能更全面!)。在.NET4.0中,您可能会将

该页面上的版本是非通用的(它是很久以前编写的),但您可能希望使其成为通用的-这样做很简单


您可以从每个“普通”线程调用
product
,从一个线程调用
Consume
,只需循环并记录它所消耗的内容。让消费者线程成为后台线程可能是最简单的,这样你就不必担心在应用程序退出时“停止”队列。这确实意味着有一种很小的可能会丢失最终的日志条目(如果在应用程序退出时写了一半的话),或者如果你的生产速度比它消耗/log的速度快,那么可能会丢失更多的日志条目。

如果你想的是一个共享队列,那么我认为你必须同步对它的写操作,推和砰的一声

但是,我仍然认为针对共享队列设计是值得的。与日志的IO相比,可能与应用程序正在进行的其他工作相比,推送和弹出的短暂阻塞量可能并不重要

我建议从测量日志记录对整个系统的实际性能影响开始(即通过运行profiler),并可以选择切换到更快的方式,例如(我很久以前就从EntLib日志记录迁移到它)

如果这不起作用,您可以尝试使用.NET Framework中的以下简单方法:

ThreadPool.QueueUserWorkItem
将方法排队以执行。该方法在线程池线程可用时执行


如果这也不起作用,那么您可以求助于John Skeet提供的东西,自己编写异步日志框架。

以下是我的想法。。。另请参见Sam Saffron的回答。如果人们在代码中看到任何问题并希望更新,那么这个答案就是CommunityWiki

/// <summary>
/// A singleton queue that manages writing log entries to the different logging sources (Enterprise Library Logging) off the executing thread.
/// This queue ensures that log entries are written in the order that they were executed and that logging is only utilizing one thread (backgroundworker) at any given time.
/// </summary>
public class AsyncLoggerQueue
{
    //create singleton instance of logger queue
    public static AsyncLoggerQueue Current = new AsyncLoggerQueue();

    private static readonly object logEntryQueueLock = new object();

    private Queue<LogEntry> _LogEntryQueue = new Queue<LogEntry>();
    private BackgroundWorker _Logger = new BackgroundWorker();

    private AsyncLoggerQueue()
    {
        //configure background worker
        _Logger.WorkerSupportsCancellation = false;
        _Logger.DoWork += new DoWorkEventHandler(_Logger_DoWork);
    }

    public void Enqueue(LogEntry le)
    {
        //lock during write
        lock (logEntryQueueLock)
        {
            _LogEntryQueue.Enqueue(le);

            //while locked check to see if the BW is running, if not start it
            if (!_Logger.IsBusy)
                _Logger.RunWorkerAsync();
        }
    }

    private void _Logger_DoWork(object sender, DoWorkEventArgs e)
    {
        while (true)
        {
            LogEntry le = null;

            bool skipEmptyCheck = false;
            lock (logEntryQueueLock)
            {
                if (_LogEntryQueue.Count <= 0) //if queue is empty than BW is done
                    return;
                else if (_LogEntryQueue.Count > 1) //if greater than 1 we can skip checking to see if anything has been enqueued during the logging operation
                    skipEmptyCheck = true;

                //dequeue the LogEntry that will be written to the log
                le = _LogEntryQueue.Dequeue();
            }

            //pass LogEntry to Enterprise Library
            Logger.Write(le);

            if (skipEmptyCheck) //if LogEntryQueue.Count was > 1 before we wrote the last LogEntry we know to continue without double checking
            {
                lock (logEntryQueueLock)
                {
                    if (_LogEntryQueue.Count <= 0) //if queue is still empty than BW is done
                        return;
                }
            }
        }
    }
}
//
///一个单例队列,用于管理从执行线程向不同日志记录源(企业库日志记录)写入日志项。
///此队列确保日志条目按照执行顺序写入,并且日志记录在任何给定时间仅使用一个线程(backgroundworker)。
/// 
公共类AsyncLoggerQueue
{
//创建记录器队列的单例实例
公共静态AsyncLoggerQueue Current=新建AsyncLoggerQueue();
私有静态只读对象logEntryQueueLock=新对象();
专用队列_LogEntryQueue=新队列();
private BackgroundWorker_Logger=新的BackgroundWorker();
专用AsyncLoggerQueue()
{
//配置后台工作程序
_Logger.worker支持扫描单元=false;
_Logger.DoWork+=新的DoWorkEventHandler(_Logger_DoWork);
}
公共无效排队(LogEntry le)
{
//写入期间锁定
锁(logEntryQueueLock)
{
_LogEntryQueue.Enqueue(le);
//锁定时,检查BW是否正在运行,如果没有,则启动它
如果(!\u Logger.IsBusy)
_Logger.RunWorkerAsync();
}
}
私有void\u Logger\u DoWork(对象发送方,DoWorkEventArgs e)
{
while(true)
{
LogEntry le=null;
bool skipmptycheck=false;
锁(logEntryQueueLock)
{
if(_LogEntryQueue.Count 1)//如果大于1,我们可以跳过检查,以查看在日志操作期间是否有任何内容已排队
skipEmptyCheck=true;
//将要写入日志的日志条目出列
le=_LogEntryQueue.Dequeue();
}
//将日志条目传递到企业库
Logger.Write(le);
if(skipmptycheck)//if LogEntryQueue.Count在我们编写最后一个日志条目之前大于1,我们知道该日志条目将在不重复检查的情况下继续
{
锁(logEntryQu)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace MediaBrowser.Library.Logging {
    public abstract class ThreadedLogger : LoggerBase {

        Queue<Action> queue = new Queue<Action>();
        AutoResetEvent hasNewItems = new AutoResetEvent(false);
        volatile bool waiting = false;

        public ThreadedLogger() : base() {
            Thread loggingThread = new Thread(new ThreadStart(ProcessQueue));
            loggingThread.IsBackground = true;
            loggingThread.Start();
        }


        void ProcessQueue() {
            while (true) {
                waiting = true;
                hasNewItems.WaitOne(10000,true);
                waiting = false;

                Queue<Action> queueCopy;
                lock (queue) {
                    queueCopy = new Queue<Action>(queue);
                    queue.Clear();
                }

                foreach (var log in queueCopy) {
                    log();
                }
            }
        }

        public override void LogMessage(LogRow row) {
            lock (queue) {
                queue.Enqueue(() => AsyncLogMessage(row));
            }
            hasNewItems.Set();
        }

        protected abstract void AsyncLogMessage(LogRow row);


        public override void Flush() {
            while (!waiting) {
                Thread.Sleep(1);
            }
        }
    }
}
public abstract class ThreadedLogger : IDisposable {

    Queue<Action> queue = new Queue<Action>();
    ManualResetEvent hasNewItems = new ManualResetEvent(false);
    ManualResetEvent terminate = new ManualResetEvent(false);
    ManualResetEvent waiting = new ManualResetEvent(false);

    Thread loggingThread; 

    public ThreadedLogger() {
        loggingThread = new Thread(new ThreadStart(ProcessQueue));
        loggingThread.IsBackground = true;
        // this is performed from a bg thread, to ensure the queue is serviced from a single thread
        loggingThread.Start();
    }


    void ProcessQueue() {
        while (true) {
            waiting.Set();
            int i = ManualResetEvent.WaitAny(new WaitHandle[] { hasNewItems, terminate });
            // terminate was signaled 
            if (i == 1) return; 
            hasNewItems.Reset();
            waiting.Reset();

            Queue<Action> queueCopy;
            lock (queue) {
                queueCopy = new Queue<Action>(queue);
                queue.Clear();
            }

            foreach (var log in queueCopy) {
                log();
            }    
        }
    }

    public void LogMessage(LogRow row) {
        lock (queue) {
            queue.Enqueue(() => AsyncLogMessage(row));
        }
        hasNewItems.Set();
    }

    protected abstract void AsyncLogMessage(LogRow row);


    public void Flush() {
        waiting.WaitOne();
    }


    public void Dispose() {
        terminate.Set();
        loggingThread.Join();
    }
}
public static void FlushLogs()
        {   
            bool queueHasValues = true;
            while (queueHasValues)
            {
                //wait for the current iteration to complete
                m_waitingThreadEvent.WaitOne();

                lock (m_loggerQueueSync)
                {
                    queueHasValues = m_loggerQueue.Count > 0;
                }
            }

            //force MEL to flush all its listeners
            foreach (MEL.LogSource logSource in MEL.Logger.Writer.TraceSources.Values)
            {                
                foreach (TraceListener listener in logSource.Listeners)
                {
                    listener.Flush();
                }
            }
        }
     public static void FlushLogs()
    {
        int queueCount;
        bool isProcessingLogs;
        while (true)
        {
            //wait for the current iteration to complete
            m_waitingThreadEvent.WaitOne();

            //check to see if we are currently processing logs
            lock (m_isProcessingLogsSync)
            {
                isProcessingLogs = m_isProcessingLogs;
            }

            //check to see if more events were added while the logger was processing the last batch
            lock (m_loggerQueueSync)
            {
                queueCount = m_loggerQueue.Count;
            }                

            if (queueCount == 0 && !isProcessingLogs)
                break;

            //since something is in the queue, reset the signal so we will not keep looping

            Thread.Sleep(400);
        }
    }
static public void LogMessageAsync(LogEntry logEntry)
{
    Task.Factory.StartNew(() => LogMessage(logEntry)); 
}