我正在尝试测试.net BlockingCollection
内部如何处理重物。
我使用一些API获取重物,并希望在多个线程中处理它。为了做到这一点,我有一个线程读取重物并将它们推入多个线程,其中每个线程都有自己的阻塞集合,每个线程都将对象从集合和进程中取出。我期待当对象从所有集合中移除时,GC应该能够清理它。清理工作没有发生,我的程序内存不足。
在两者之间调用GC.Collect()
正在帮助我完成整个过程,但它有很大的性能损失,我承担不起。
我唯一的问题是为什么即使对象超出范围,垃圾收集器也无法释放资源。
public class DummyProcessor
{
List<BlockingCollection<object>> listOfBlockingCollection = null;
void ProcessCollection(object blockingCollection)
{
BlockingCollection<object> collection = (BlockingCollection<object>)blockingCollection;
while (collection.IsCompleted == false)
{
object heavyObject = collection.Take();
CallExternalProcess(heavyObject);
}
}
private void CallExternalProcess(object heavyObject)
{
throw new NotImplementedException();
}
public void Analyze(object heavyObject)
{
if (listOfBlockingCollection == null)
{
listOfBlockingCollection = new List<BlockingCollection<object>>();
for (int i = 0; i < 25; i++)
{
BlockingCollection<object> coll = new BlockingCollection<object>();
listOfBlockingCollection.Add(coll);
Thread pt = new Thread(new ParameterizedThreadStart(ProcessCollection));
pt.Start(coll);
}
}
for (int i = 0; i < 25; i++)
{
listOfBlockingCollection[i].Add(heavyObject);
}
}
}
答案 0 :(得分:2)
如果以线程安全的方式调用Analyze
,我现在无法在此代码中看到任何威胁。如果dotMemory有一些分析,请将其添加到问题中。
但是,我想指出你调用Analyze
方法的方式。首先,您以非线程安全的方式初始化listOfBlockingCollection
变量,因为如果您有两个运行Analyze
方法的线程,则可以使竞争和成为一种情况其中多个线程在代码中运行if
子句。
在这种情况下,您创建了至少25个鬼线程,更重要的是,您可以了解情况,然后您的listOfBlockingCollection
超过25个项:如果某个线程已经输出if
子句,而另一个仍然创建线程并将集合添加到列表中(例如,您可以查看listOfBlockingCollection.Count
)。一个线程在内存中至少为2 MB,而作为IDisposable
对象,它的收集速度并不是您想要的那么快。
您的代码的其他问题是25
线程是一个无效的线程数(只有当您拥有一个具有32个核心的超级计算机时,这可能是一个选项),因为上下文切换。最好使用等于系统内核数量的线程数,或者更好的是,将代码切换到面向Task
(您可以轻松地为处理创建25个任务,并在其中循环)。
另一个选择,如果您需要一些额外的数据工作流程,您可以在应用程序中使用25 TPL DataFlow的ActionBlocks
库,启动25个不同的流程。完成重型对象队列后,您可以轻松send the Complete
command遍布各个块并完成执行。
答案 1 :(得分:1)
通过dotMemory运行应用程序后,我发现生成的重物太快,以至于它们被累积到计算机内存之外的大量BlockingCollection
中。
我对GC.Collect
的显式调用并不是因为它正在释放使用过的重对象,而是为重型对象生成添加了一些暂停,为消费者线程提供了更多时间来清除BlockingCollection
中已有的内容。
所以我不得不介绍重型对象生产者和消费者之间的等待,这样我就不会过度使用可用内存。为此我使用AutoResetEvent
。我在收集大小达到某个范围后调用AutoResetEvent.WaitOne
,一旦它低于我调用AutoResetEvent.Set
的范围,再次运行生产者。
感谢大家对此的意见。