How to effectively log asynchronously?

前端 未结 10 1086
无人及你
无人及你 2020-12-07 07:28

I am using Enterprise Library 4 on one of my projects for logging (and other purposes). I\'ve noticed that there is some cost to the logging that I am doing that I can miti

10条回答
  •  一向
    一向 (楼主)
    2020-12-07 07:55

    I wrote this code a while back, feel free to use it.

    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 queue = new Queue();
            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 queueCopy;
                    lock (queue) {
                        queueCopy = new Queue(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);
                }
            }
        }
    }
    

    Some advantages:

    • It keeps the background logger alive, so it does not need to spin up and spin down threads.
    • It uses a single thread to service the queue, which means there will never be a situation where 100 threads are servicing the queue.
    • It copies the queues to ensure the queue is not blocked while the log operation is performed
    • It uses an AutoResetEvent to ensure the bg thread is in a wait state
    • It is, IMHO, very easy to follow

    Here is a slightly improved version, keep in mind I performed very little testing on it, but it does address a few minor issues.

    public abstract class ThreadedLogger : IDisposable {
    
        Queue queue = new Queue();
        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 queueCopy;
                lock (queue) {
                    queueCopy = new Queue(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();
        }
    }
    

    Advantages over the original:

    • It's disposable, so you can get rid of the async logger
    • The flush semantics are improved
    • It will respond slightly better to a burst followed by silence

提交回复
热议问题