gpt4 book ai didi

c# - 如何使用 BlockingCollection<> 解决生产者/消费者竞争条件

转载 作者:太空狗 更新时间:2023-10-30 01:04:01 27 4
gpt4 key购买 nike

我正在实现一个将记录写入数据库的记录器。为了防止数据库写入阻塞调用记录器的代码,我将数据库访问移到了一个单独的线程,使用基于 BlockingCollection<string> 的生产者/消费者模型实现。 .

这是简化的实现:

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);
}
}

现在,当我的应用程序关闭时,我需要确保在数据库连接断开之前刷新缓冲区。否则,WriteToDb将抛出异常。

所以,这是我天真的 Flush 实现:

public void Flush()
{
// Sleep until the buffer is empty.
while(this._buffer.Count > 0)
{
Thread.Sleep(50);
}
}

此实现的问题在于以下事件序列:

  1. 缓冲区中只有一个条目。
  2. 在记录线程中,MoveNext()在枚举器上被调用,所以我们现在在 ProcessBuffer 的正文中的 foreach循环。
  3. Flush()被主线程调用。它看到集合为空,因此立即返回。
  4. 主线程关闭数据库连接。
  5. 回到日志线程,foreach 的正文循环开始执行。 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()方法可以在集合为空之后但在 _isWritingBuffer 之前执行设置为 true .

如何修复我的 Flush实现以避免这种竞争条件?

注意:由于各种原因,我必须从头开始编写记录器,因此请不要建议我使用现有的日志记录框架。

最佳答案

首先永远不要锁定公共(public)对象,尤其是this

此外,永远不要使用纯 bool 值进行同步:如果您想了解可能出错的地方,请参阅我的博客: Synchronization, memory visibility and leaky abstractions :)

关于问题本身,我一定遗漏了一些东西,但为什么您需要这样的 Flush 方法?

实际上,当您完成日志记录后,您将通过从主线程调用其 Dispose 方法来处理记录器。

并且您已经以等待“写入数据库”任务的方式实现它。

如果我错了,你真的需要与另一个原语同步,那么你应该使用一个事件:

DbLogger 中:

public ManualResetEvent finalizing { get; set; }

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

在某个地方,例如在 ProcessBuffer 中,您会在完成写入 DB 时收到通知:

finalizing.Set();

关于c# - 如何使用 BlockingCollection<> 解决生产者/消费者竞争条件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25168113/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com