![](/img/trans.png)
[英]Multithreading Multiple Producer and Consumer Threads Won't Sync BlockingCollection Race Condition
[英]How to solve producer/consumer race condition with BlockingCollection<>
我正在實現一個記錄器,它將記錄寫入數據庫。 為了防止數據庫寫入阻塞調用記錄器的代碼,我已經將數據庫訪問移動到一個單獨的線程,使用基於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);
}
}
此實現的問題是以下事件序列:
MoveNext()
,因此我們現在處於ProcessBuffer
的foreach
循環體中。 Flush()
由主線程調用。 它看到該集合為空,因此立即返回。 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
實現以避免這種競爭條件?
注意:由於各種原因,我必須從頭開始編寫記錄器,所以請不要回答我使用現有記錄框架的建議。
首先永遠不要鎖定公共對象 ,尤其是this
。
此外, 永遠不要使用裸布線進行同步 :如果您想了解可能出現的問題,請查看我的博客: 同步,內存可見性和漏洞抽象 :)
關於這個問題本身我一定會遺漏一些東西,但為什么你需要這樣的Flush
方法呢?
實際上,當您完成日志記錄后,您將通過從主線程調用其Dispose
方法來處置記錄器。
並且您已經以這樣的方式實現它,它將等待“寫入DB”任務。
如果我錯了,你真的需要與另一個原語同步,那么你應該使用一個事件:
在DbLogger
:
public ManualResetEvent finalizing { get; set; }
public void Flush()
{
finalizing.WaitOne();
}
在某處,例如在ProcessBuffer
,當您完成對DB的寫入時,您會通知:
finalizing.Set();
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.