Eri*_*ver 32 c# logging multithreading enterprise-library
我在我的一个项目中使用Enterprise Library 4进行日志记录(以及其他用途).我注意到我正在做的日志记录有一些成本,我可以通过在单独的线程上进行日志记录来减轻这种成本.
我现在这样做的方法是创建一个LogEntry对象,然后在调用Logger.Write的委托上调用BeginInvoke.
new Action<LogEntry>(Logger.Write).BeginInvoke(le, null, null);
Run Code Online (Sandbox Code Playgroud)
我真正想做的是将日志消息添加到队列中,然后让一个线程将LogEntry实例从队列中拉出并执行日志操作.这样做的好处是日志记录不会干扰执行操作,并且并非每个日志记录操作都会导致在线程池上抛出作业.
如何以线程安全的方式创建支持多个编写器和一个读取器的共享队列?设计用于支持许多编写器(不会导致同步/阻塞)和单个读取器的队列实现的一些示例将非常受欢迎.
关于替代方法的建议也将受到赞赏,但我对改变日志框架并不感兴趣.
Sam*_*ron 35
我不久前写了这段代码,随意使用它.
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);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
一些优点:
这是一个稍微改进的版本,请记住,我对它进行了很少的测试,但它确实解决了一些小问题.
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();
}
}
Run Code Online (Sandbox Code Playgroud)
优于原版:
Jon*_*eet 11
是的,您需要一个生产者/消费者队列.我在我的线程教程中有一个这样的例子 - 如果你查看我的"死锁/监视方法"页面,你会在下半部分找到代码.
当然,网上还有很多其他的例子 - 而.NET 4.0也将在框架中附带一个(比我的更多功能!).在.NET 4.0中,你可能会包装ConcurrentQueue<T>一个BlockingCollection<T>.
该页面上的版本是非泛型的(它是很久以前写的)但你可能想让它变得通用 - 这样做很简单.
你可以Produce从每个"普通"线程调用,Consume从一个线程调用,只是循环并记录它消耗的任何东西.将消费者线程作为后台线程可能是最简单的,因此您不必担心在应用程序退出时"停止"队列.这确实意味着很可能错过了最终的日志条目(如果它是在应用程序退出时写入它的一半) - 或者甚至更多,如果你的生产速度超过消耗/日志的速度.