BlockingCollection (コードは以下) に基づいたプロデューサー コンシューマー パターンを持つ単純なロガーがあります。
public class Logger
{
public Logger()
{
_messages = new BlockingCollection<LogMessage>(int.MaxValue);
_worker = new Thread(Work) {IsBackground = true};
_worker.Start();
}
~Logger()
{
_messages.CompleteAdding();
_worker.Join(); // Wait for the consumer's thread to finish.
//Some logic on closing log file
}
/// <summary>
/// This is message consumer thread
/// </summary>
private void Work()
{
while (!_messages.IsCompleted)
{
//Try to get data from queue
LogMessage message;
try
{
message = _messages.Take();
}
catch (ObjectDisposedException) { break; } //The BlockingCollection(Of T) has been disposed.
catch(InvalidOperationException){ continue; } //the BlockingCollection(Of T) is empty and the collection has been marked as complete for adding.
//... some simple logic to write 'message'
}
}
}
問題は、アプリケーションがそれですぐに終了しないことです。アプリケーションを終了するのに 20 ~ 40 秒かかります。途中でデバッガーを一時停止すると、次のことが
わかります。
2. _worker スレッドは _messages.Take() にあります。
_messages.Take() が _messages.CompleteAdding(); の直後に終了するのを待ちます。しかし、そうではないようです。
このファイナライズの何が問題で、この状況でワーカー スレッドをより適切にファイナライズする方法を教えてください。
PS私は単純に _worker.Join() を削除できますが、 Work() は閉じたファイルに何かを書き込むことができます。つまり、これは同時非決定状況です。
更新
概念の証明として、名前を ~Logger() から Close() に変更し、ある時点で呼び出します。ロガーを即座に閉じます。したがって、この場合、予想どおり _messages.Take() は _messages.CompleteAdding() の直後に終了します。
~Logger での 20 ~ 40 秒の遅延の唯一の説明は、GC スレッドの優先度が高いことです。別の説明がありますか?