Posible pérdida de memoria en ConcurrentBag?

He estado leyendo las nuevas colecciones concurrentes y, especialmente, el ConcurrentBag me llamó la atención. Dado que el ConcurrentBag contiene internamente un conjunto local en cada subproceso individual que lo usa para realizar un seguimiento de los elementos, esto significa que cuando el subproceso se sale del scope, seguirá siendo referenciado en la memoria por el ConcurrentBag. Esto a su vez significa tanto la memoria reclamada por el hilo, como los recursos nativos? (discúlpeme por no conocer el funcionamiento interno exacto del objeto de subproceso .NET)

Puedo suponer un caso de uso donde tienes 1 ConcurrentBack global para un servicio web multiproceso en el que tienes muchos clientes que agregan tareas. Estas tareas se agregan por subprocesos en el grupo de subprocesos. Ahora el subproceso de subprocesos es una forma muy eficiente de gestionar subprocesos, pero elimina y crea subprocesos en función de la cantidad de trabajo. Por lo tanto, dicho servicio web a veces puede encontrarse en problemas ya que la bolsa subyacente aún hace referencia a muchos hilos que deberían destruirse.

Creé una aplicación rápida para probar este comportamiento:

static ConcurrentBag bag = new ConcurrentBag(); static void FillBag() { for (int i = 0; i  { FillBag(); PrintState(); }); // empty bag PrintState(); // first 100 items are added on main thread FillBag(); PrintState(); // second 100 items are added on remote thread remote.Start(); remote.Join(); // since the remote thread is gone out of scope, what happened to its local storage which is part of the bag? PrintState(); // now force a cleanup WeakReference weakRemoteReference = new WeakReference(remote); remote = null; GC.Collect(); GC.WaitForPendingFinalizers(); // Now check if the thread still exists if (weakRemoteReference.IsAlive) Console.WriteLine("Remote thread still exists"); PrintState(); Console.ReadLine(); 

Y la salida confirma mi historia:

 Bag size is: 0 Bag size is: 100 Bag size is: 200 Bag size is: 200 Remote thread still exists Bag size is: 200 

¿Es de esperar este comportamiento? ¿Cometí un error en mi prueba o puede considerarse una falla de diseño?

ConcurrentBag realmente mantiene las cosas en el almacenamiento local de la secuencia, y si abandona los hilos puede causar una pérdida de memoria. Sin embargo, la implementación puede “robar” elementos de la lista de un hilo para darlos a otro hilo. Puedes ver esto en acción si escribes lo siguiente:

 ConcurrentBag MyBag = new ConcurrentBag(); void DoIt() { for (int i = 0; i < 10; ++i) { MyBag.Add(i); } ThreadPool.QueueUserWorkItem(EmptyBag); Console.Write("Press Enter:"); Console.ReadLine(); Console.WriteLine("{0} items in bag", MyBag.Count); } void EmptyBag(object state) { int take; while (MyBag.TryTake(out take)) { Console.WriteLine(take); } Console.WriteLine("Bag is empty"); } 

Si ejecuta ese progtwig y espera hasta que aparezca el mensaje "La bolsa está vacía" antes de presionar Enter, verá que la bolsa está vacía.

Entonces, mientras haya un hilo leyendo de la bolsa, se vaciará eventualmente. Incluso si todos los elementos fueron agregados por otros hilos.

Entonces, sí, hay una posible pérdida de memoria. En la práctica, sin embargo, si múltiples hilos están accediendo a la bolsa, es probable que no sea una preocupación.