我有一个无限循环,用于使用BlockingCollection中的项目。
public class MessageFileLogger
{
private BlockingCollection<ILogItem> _messageQueue;
private Thread _worker;
private bool _enabled = false;
public MessageFileLogger()
{
_worker = new Thread(LogMessage);
_worker.IsBackground = true;
_worker.Start();
}
private void LogMessage()
{
while (_enabled)
{
if (_messageQueue.Count > 0)
{
itm = _messageQueue.Take();
processItem(itm);
}
else
{
Thread.Sleep(1000);
}
}
}
}
由另一个每隔一分钟或几分钟实例化的对象引用(可以移出1小时或者等等)
public class Helper
{
MessageFileLogger _logger;
public Helper(string logFilePath, LogMode logMode)
{
_logger = new MessageFileLogger(logFilePath, logMode);
_logger.Enabled = true;
}
public void foo()
{
}
}
问题#1) 当不再需要引用该线程的对象时,我该怎么做才能确保退出该线程。
注意:Helper只需要调用foo,所以一旦它不再需要调用foo,就可以对对象进行垃圾回收。因此,在Helper中加入using语句肯定是可能的。
问题#2) 是否需要处理_messageQueue?如果是这样,如何在不影响LogMessage线程的情况下处理它? (我尝试在线程运行时处理它并且毫不奇怪出错)
我尝试扩展IDisposable(在MessageFileLogger中):
public void Dispose()
{
_enabled = false;
_messageQueue.Dispose();
}
我对此没有任何问题,但我不确定我还没有遇到任何问题。此外,这是否意味着Helper还需要IDisposable并且需要将一个using语句与Helper一起使用?
注意:此问题基于我与another question的相同代码。
答案 0 :(得分:6)
首先,您的消费者不应该致电Thread.Sleep
。它当然也不应该检查集合的数量。 BlockingCollection
的整个点就是当你致电Take
时,它会给你和物品,或者等待有物品给你,然后把它交给你。所以你可以继续在一个循环中调用Take
。这可以防止您在可能正在处理的项目时等待几分之一秒。
更好的是,您只需使用GetConsumingEnumerable
来获取一系列项目。
您的消费者现在看起来像这样:
foreach(var item in _messageQueue.GetConsumingEnumerable())
processItem(item);
此外,BlockingCollection
已内置支持指示队列已完成。只需让制作人调用CompleteAdding
即可表示不再添加任何项目。执行此操作后,一旦队列为空,Enumerable
将结束,foreach
循环将结束。消费者可以在那个时间点进行任何必要的清理工作。
除了使用BlockingCollection
确定何时完成这一事实通常更方便的事实,它也是正确的,与您的代码不同。由于_enabled
不易变,即使你从不同的线程读取和写入它,你也没有引入适当的内存障碍,因此消费者很可能正在读取该变量的陈旧值一段时间当您使用专门设计用于处理这些类型的多线程情况的BCL机制时,您可以确保代表您正确处理它们,而无需考虑它们。