我使用BlockingCollection
在C#4.0中实现生产者 - 消费者模式。
BlockingCollection
包含占用大量内存的项目。我想让生产者一次从BlockingCollection中取出一个项目并进行处理。
我想通过在BlockingCollection.GetConsumingEnumerable()
上使用foreach,BlockingCollection
每次都会从底层队列中删除项目(这意味着所有这些都与引用一起),所以在进程结束时()处理项目的方法,该项目可以被垃圾收集。
但事实并非如此。似乎BlockingCollection.GetConsumingEnumerable()
上的foreach循环确实包含了输入队列的所有项目的引用。所有物品都被保留(从而防止被垃圾收集),直到走出foreach循环。
我没有在BlockingCollection.GetConsumingEnumerable()
上使用简单的foreach循环,而是使用while循环测试BlockingCollection.IsComplete标记,并在循环内使用BlockingCollection.Take()
来获取耗材项。我假设BlockingCollection.Take()
具有与List.Remove()
类似的效果,它将从BlockingCollection中删除该项的引用。但这又是错误的。所有项目都只是在while循环之外收集的垃圾。
所以我的问题是,我们如何轻松实现这一要求,以便BlockingCollection可能保留消耗内存的项目,并且每个项目一旦被消费者使用就可以进行垃圾回收?非常感谢您的帮助。
编辑:根据要求,添加了一个简单的演示代码:
// Entity is what we are going to process.
// The finalizer will tell us when Entity is going to be garbage collected.
class Entity
{
private static int counter_;
private int id_;
public int ID { get{ return id_; } }
public Entity() { id_ = counter++; }
~Entity() { Console.WriteLine("Destroying entity {0}.", id_); }
}
...
private BlockingCollection<Entity> jobQueue_ = new BlockingCollection<Entity>();
private List<Task> tasks_ = new List<Task>();
// This is the method to launch and wait for the tasks to finish the work.
void Run()
{
tasks_.Add(Task.Factory.StartNew(ProduceEntity);
Console.WriteLine("Start processing.");
tasks_.Add(Task.Factory.StartNew(ConsumeEntity);
Task.WaitAll(tasks_.ToArray());
}
// The producer creates Entity instances and add them to BlockingCollection.
void ProduceEntity()
{
for(int i = 0; i < 10; i ++) // We are adding totally 10 entities.
{
var newEntity = new Entity();
Console.WriteLine("Create entity {0}.", newEntity.ID);
jobQueue_.Add(newEntity);
}
jobQueue_.CompleteAdding();
}
// The consumer takes entity, process it (and what I need: destroy it).
void ConsumeEntity()
{
while(!jobQueue_.IsCompleted){
Entity entity;
if(jobQueue_.TryTake(entity))
{
Console.WriteLine("Process entity {0}.", entity.ID);
entity = null;
// I would assume after GC, the entity will be finalized and garbage collected, but NOT.
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
Console.WriteLine("Finish processing.");
}
输出是所有创建和处理消息,然后是“完成处理”。然后是来自实体的所有销毁消息。并且创建实体消息显示从0到9的Entity.ID以及显示从9到0的Entity.ID的销毁消息。
编辑:
即使我设置了BlockingCollection的绑定容量,所有进入它的项目只有在循环退出时才会完成,这很奇怪。
答案 0 :(得分:6)
ConcurrentQueue包含内部数组为32个项的段。在段被垃圾收集之前,实体项不会被垃圾收集。从队列中取出所有32个项目后会发生这种情况。如果您更改示例以添加32个项目,您将在“完成处理”之前看到“销毁实体”消息。
答案 1 :(得分:2)
BlockingCollection是否继续保持引用取决于它正在使用的集合类型。
BlockingCollection<T>
的{{3}}为ConcurrentQueue<T>
。
因此垃圾收集行为将取决于集合类型。在ConcurrentQueue<T
&gt;的情况下这是一个FIFO结构,所以如果它从队列中删除后没有从数据结构中释放引用(这是队列的定义),我会非常惊讶!
您究竟如何确定对象没有被垃圾收集?
答案 2 :(得分:1)