处理具有无限循环的线程



我有一个无限循环,用于消耗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中专门设计用于处理这些类型的多线程情况的机制时,您可以确保它们将以您的名义得到正确处理,而无需考虑它们。

最新更新