我有一个无限循环,用于消耗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
,就可以对对象进行垃圾回收。因此,将using语句与Helper结合起来当然是可能的。
问题2)是否需要处理_messageQueue
?如果是,如何在不影响LogMessage
线程的情况下处理它?(我在线程运行时尝试处理它,出现错误也就不足为奇了)。
我尝试扩展IDisposable(在MessageFileLogger中):
public void Dispose()
{
_enabled = false;
_messageQueue.Dispose();
}
我对此没有任何问题,但我不相信我只是还没有问题。此外,这是否意味着Helper
也需要IDisposable
,而using语句需要与Helper
一起使用?
注意:这个问题是基于与我的另一个问题相同的代码。
首先,您的消费者不应该调用Thread.Sleep
。当然,它也不应该检查集合的计数。BlockingCollection
的整个点是,当您调用Take
时,它要么给您和项目,要么等待,直到有项目给您,然后再给您。因此,您可以继续循环调用Take
,而不需要其他操作。这可以防止您在已经有可以处理的项目时等待几分之一秒。
更好的是,您可以简单地使用GetConsumingEnumerable
来获取项目序列。
你的消费者现在可以像这样:
foreach(var item in _messageQueue.GetConsumingEnumerable())
processItem(item);
此外,BlockingCollection
内置了对指示队列已完成的支持。只需让生产者调用CompleteAdding
来指示不再添加任何项目。之后,一旦队列为空,Enumerable
将结束,foreach
循环将结束。消费者可以在该时间点进行任何需要的清理。
除了使用BlockingCollection
来确定何时完成通常更方便之外,与代码不同,它也是正确的。由于_enabled
不是易失性的,即使您从不同的线程对其进行读写,也没有引入适当的内存屏障,因此消费者可能会在一段时间内读取该变量的陈旧值。当您使用BCL中专门设计用于处理这些类型的多线程情况的机制时,您可以确保它们将以您的名义得到正确处理,而无需考虑它们。