6

BlockingCollectionC# 4.0 でプロデューサー/コンシューマー パターンを実装するために a を使用します。

BlockingCollection、かなり多くのメモリを消費するアイテムを保持します。プロデューサーが BlockingCollection から一度に 1 つのアイテムを取り出して処理できるようにしたいと考えています。

foreach on を使用するBlockingCollection.GetConsumingEnumerable()たびにBlockingCollection、基礎となるキューからアイテムが削除されるので(つまり、参照と一緒にすべてを意味します)、アイテムを処理する Process() メソッドの最後に、アイテムがガベージになる可能性があると考えていました集めました。

しかし、これは真実ではありません。foreach ループはBlockingCollection.GetConsumingEnumerable()、キューに入力されたアイテムのすべての参照を保持しているようです。foreach ループから出るまで、すべての項目が保持されます (したがって、ガベージ コレクションが防止されます)。

で単純な foreach ループを使用する代わりに、BlockingCollection.GetConsumingEnumerable()BlockingCollection.IsComplete フラグをテストする while ループを使用し、ループ内BlockingCollection.Take()で消耗品を取得します。BlockingCollection からアイテムの参照を削除する とBlockingCollection.Take()同様の効果があると思います。List.Remove()しかし、これもまた間違っています。すべてのアイテムは、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 のバインドされた容量を設定しても、これまでに入力されたすべてのアイテムは、ループが終了したときにのみ確定されます。これは奇妙です。

4

3 に答える 3

6

ConcurrentQueueには、32項目の内部配列を持つセグメントが含まれています。セグメントがガベージコレクションされるまで、エンティティアイテムはガベージコレクションされません。これは、32個のアイテムすべてがキューから取り出された後に発生します。例を変更して32個のアイテムを追加すると、「処理の終了」の前に「エンティティの破棄」メッセージが表示されます。

于 2010-11-08T12:23:01.697 に答える
2

BlockingCollection が引き続き参照を保持するかどうかは、使用しているコレクション タイプによって異なります。

デフォルトのコレクション タイプBlockingCollection<T>ですConcurrentQueue<T>

したがって、ガベージ コレクションの動作はコレクションの種類によって異なります。>の場合、ConcurrentQueue<Tこれは FIFO 構造なので、参照がキューから削除された後にデータ構造から参照が解放されなかったとしたら、非常に驚​​きます (キューの定義のようなものです)。

オブジェクトがガベージ コレクションされていないことをどの程度正確に判断していますか?

于 2010-10-21T05:54:28.440 に答える