C# 任务并行库中的BlockingCollection不会自动释放底层实例的引用

C# 任务并行库中的BlockingCollection不会自动释放底层实例的引用,c#,multithreading,task-parallel-library,C#,Multithreading,Task Parallel Library,我使用BlockingCollection在C#4.0中实现生产者-消费者模式 BlockingCollection包含占用大量内存的项。我想让制作人一次从BlockingCollection中取出一个项目,并对其进行处理 我在想,通过在BlockingCollection.getConsumineumerable()上使用foreach,每次BlockingCollection都会从底层队列中删除该项(这意味着所有内容都与引用一起),因此在处理该项的Process()方法的末尾,可以对该项进行

我使用
BlockingCollection
在C#4.0中实现生产者-消费者模式

BlockingCollection
包含占用大量内存的项。我想让制作人一次从BlockingCollection中取出一个项目,并对其进行处理

我在想,通过在
BlockingCollection.getConsumineumerable()
上使用foreach,每次
BlockingCollection
都会从底层队列中删除该项(这意味着所有内容都与引用一起),因此在处理该项的Process()方法的末尾,可以对该项进行垃圾收集

但事实并非如此。
BlockingCollection.getConsumineumerable()
上的foreach循环似乎保存了输入队列的项目的所有引用。所有项目都被保留(因此不会被垃圾收集),直到跳出foreach循环

我使用while循环testing BlockingCollection.IsComplete标志,在循环内部使用
BlockingCollection.Take()
,而不是在
BlockingCollection.getConsumineurable()上使用简单的foreach循环。我假设
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.");
}
//实体是我们要处理的对象。
//终结器将告诉我们何时对实体进行垃圾收集。
类实体
{
专用静态int计数器;
私有内部id;
公共int-ID{get{return-ID}
公共实体()
~Entity(){Console.WriteLine(“销毁实体{0}.”,id_417;}
}
...
private BlockingCollection作业队列=新建BlockingCollection();
私有列表任务\=new List();
//这是启动并等待任务完成工作的方法。
无效运行()
{
tasks.Add(Task.Factory.StartNew(ProduceEntity);
Console.WriteLine(“开始处理”);
tasks.Add(Task.Factory.StartNew(consumerentity);
Task.WaitAll(tasks.ToArray());
}
//生产者创建实体实例并将其添加到BlockingCollection。
作废生产实体()
{
对于(inti=0;i<10;i++)//我们总共添加了10个实体。
{
var newEntity=新实体();
WriteLine(“创建实体{0}.”,newEntity.ID);
jobQueue.Add(newEntity);
}
jobQueue_u2;.CompleteAdding();
}
//消费者获取实体,处理它(我需要的是:销毁它)。
无效实体()
{
而(!jobQueue_u2;已完成){
实体;
if(jobQueue_u.TryTake(实体))
{
WriteLine(“进程实体{0}.”,entity.ID);
实体=空;
//我假设在GC之后,实体将被最终确定并被垃圾收集,但不会。
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
控制台.WriteLine(“完成处理”);
}
输出是所有创建和处理消息,后跟“Finish processing.”,后跟来自实体的所有销毁消息。创建实体消息显示Entity.ID从0到9,销毁消息显示Entity.ID从9到0

编辑:


即使在我设置BlockingCollection的绑定容量时,所有进入它的项目都只有在循环退出时才最终确定,这很奇怪。

BlockingCollection是否继续保留引用取决于它使用的集合类型

BlockingCollection
的目标是
ConcurrentQueue

因此,垃圾收集行为将取决于收集类型。在
ConcurrentQueue的情况下,这是一个FIFO结构,因此,如果从队列中删除引用后,没有从数据结构中释放引用(这是队列的一种定义),我会非常惊讶


如何准确确定对象未被垃圾收集?

ConcurrentQueue包含具有32项内部数组的段。在段被垃圾收集之前,实体项不会被垃圾收集。这将在所有32项从队列中取出后发生。如果将示例更改为添加32项,则ou将在“完成处理”之前看到“销毁实体”消息

请参见

仅仅因为没有持有ref并不意味着GC将立即介入并收集它…使用适当的GC.collect等方法演示您的问题的示例代码将很有帮助。这对我来说是一种奇怪的行为。我不能确保每次都有那么多项目。但我确实按照您的描述尝试了它.我想我每次消费一件物品时都必须手动释放每件物品所拥有的内存。你的答案很接近最终答案。但我会等着看其他人是否能想出一个解决方案来应对这种奇怪的行为。