C# 如何利用BlockingCollection解决生产者/消费者竞争状况<&燃气轮机;
我正在实现一个将记录写入数据库的记录器。为了防止数据库写入阻塞调用记录器的代码,我将DB访问移到了一个单独的线程,使用基于C# 如何利用BlockingCollection解决生产者/消费者竞争状况<&燃气轮机;,c#,.net,multithreading,thread-safety,C#,.net,Multithreading,Thread Safety,我正在实现一个将记录写入数据库的记录器。为了防止数据库写入阻塞调用记录器的代码,我将DB访问移到了一个单独的线程,使用基于BlockingCollection的生产者/消费者模型实现 以下是简化的实现: abstract class DbLogger : TraceListener { private readonly BlockingCollection<string> _buffer; private readonly Task _writerTask;
BlockingCollection
的生产者/消费者模型实现
以下是简化的实现:
abstract class DbLogger : TraceListener
{
private readonly BlockingCollection<string> _buffer;
private readonly Task _writerTask;
DbLogger()
{
this._buffer = new BlockingCollection<string>(new ConcurrentQueue<string>(), 1000);
this._writerTask = Task.Factory.StartNew(this.ProcessBuffer, TaskCreationOptions.LongRunning);
}
// Enqueue the msg.
public void LogMessage(string msg) { this._buffer.Add(msg); }
private void ProcessBuffer()
{
foreach (string msg in this._buffer.GetConsumingEnumerable())
{
this.WriteToDb(msg);
}
}
protected abstract void WriteToDb(string msg);
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Signal to the blocking collection that the enumerator is done.
this._buffer.CompleteAdding();
// Wait for any in-progress writes to finish.
this._writerTask.Wait(timeout);
this._buffer.Dispose();
}
base.Dispose(disposing);
}
}
public void Flush()
{
// Sleep until the buffer is empty.
while(this._buffer.Count > 0)
{
Thread.Sleep(50);
}
}
此实现的问题在于以下事件顺序:
MoveNext()
,因此我们现在处于ProcessBuffer
的foreach
循环体中Flush()
由主线程调用。它看到集合为空,因此立即返回foreach
循环的主体开始执行<调用code>WriteToDb,但由于数据库连接已关闭而失败private volatile bool _isWritingBuffer = false;
private void ProcessBuffer()
{
foreach (string msg in this._buffer.GetConsumingEnumerable())
{
lock (something) this._isWritingBuffer = true;
this.WriteToDb(msg);
lock (something) this._isWritingBuffer = false;
}
}
public void Flush()
{
// Sleep until the buffer is empty.
bool isWritingBuffer;
lock(something) isWritingBuffer = this._isWritingBuffer;
while(this._buffer.Count > 0 || isWritingBuffer)
{
Thread.Sleep(50);
}
}
但是,仍然存在争用条件,因为整个Flush()
方法可以在集合为空之后但\u isWritingBuffer
设置为true
之前执行
如何修复我的Flush
实现以避免这种争用情况
注意:由于各种原因,我必须从头开始编写记录器,因此请不要建议我使用一些现有的日志框架。首先永远不要锁定公共对象,尤其是
这个
此外,永远不要使用纯布尔值进行同步:如果您想了解可能出现的问题,请查看我的博客::)
关于问题本身,我肯定遗漏了一些东西,但为什么您需要这样一种Flush
方法
实际上,当您完成日志记录时,您将通过从主线程调用其dispose
方法来处理记录器
您已经以这样的方式实现了它,它将等待“write to DB”任务
如果我错了,并且您确实需要与另一个原语同步,那么您应该使用一个事件:
在DbLogger
中:
public ManualResetEvent finalizing { get; set; }
public void Flush()
{
finalizing.WaitOne();
}
在某个地方,例如在ProcessBuffer
中,当您完成对数据库的写入时,您会通知:
finalizing.Set();
为什么不锁定(这个)这个呢;并锁定(这个)这个。_isWritingBuffer=false;在foreach循环之外?因此,您假设在知道集合为空之前一直在写?@tolanj:这不起作用,因为枚举器正在阻塞。因此,如果集合中没有任何内容,那么它就放在那里(直到CompleteAdding()为止)调用了
。如果我将锁移到foreach循环之外,Flush
将永远不会返回,直到记录器被释放。我不了解您的所有代码,但为什么您要这么难?文档中有一些简单的示例。我想您是对的-我可能根本不需要Flush方法。我只需要确保记录器在DB连接消失之前处理(目前,这两个操作的顺序是任意的)。至于您的前两个注释,我已经更正了它们(它们仅在我发布的原始示例中不正确)