c# - remarks - ¿Recolección concurrente que apoya la eliminación de un artículo específico?
returns c# (3)
Bastante simple: aparte de ConcurrentDictionary (que usaré si tengo que hacerlo pero no es realmente el concepto correcto), ¿existe alguna colección Concurrent (implementación de IProducerConsumer) que admita la eliminación de elementos específicos en función de la simple igualdad de un elemento o un predicado? definiendo una condición para la eliminación?
Explicación: Tengo un algoritmo de flujo de trabajo de varias etapas y múltiples subprocesos, que extrae objetos de la base de datos y los pega en una cola de "inicio". A partir de ahí, son agarrados por la siguiente etapa, se trabajan más y se rellenan en otras colas. Este proceso continúa a través de unas cuantas etapas más. Mientras tanto, la primera etapa es invocada nuevamente por su supervisor y extrae objetos de la base de datos, y pueden incluir objetos aún en proceso (porque no han terminado de procesarse y, por lo tanto, no se han vuelto a persistir con el indicador de conjunto de marcas). están terminados).
La solución que estoy diseñando es una colección maestra "en obra"; los objetos entran en esa cola cuando se recuperan para su procesamiento en la primera etapa, y se eliminan después de que se hayan vuelto a guardar en la base de datos como "procesados" en cualquier etapa del flujo de trabajo que haya completado el procesamiento necesario. Mientras el objeto esté en esa lista, se ignorará si la primera etapa lo recupera.
Había planeado usar un ConcurrentBag, pero el único método de eliminación (TryTake) elimina un elemento arbitrario de la bolsa, no uno específico (y ConcurrentBag es lento en .NET 4). ConcurrentQueue y ConcurrentStack tampoco permiten la eliminación de un elemento que no sea el siguiente que le proporcionará, dejando ConcurrentDictionary, que funcionaría pero es más de lo que necesito (todo lo que necesito es almacenar la identificación de los registros que se procesan; no cambian durante el flujo de trabajo).
Como ya se explicó en otras publicaciones, no es posible eliminar elementos de una Queue
o ConcurrentQueue
de forma predeterminada, pero en realidad la forma más fácil de moverse es extender o ajustar el elemento.
public class QueueItem
{
public Boolean IsRemoved { get; private set; }
public void Remove() { IsRemoved = true; }
}
Y cuando encolado:
QueueItem item = _Queue.Dequeue(); // Or TryDequeue if you use a concurrent dictionary
if (!item.IsRemoved)
{
// Do work here
}
Es realmente difícil hacer una colección segura para subprocesos en el sentido genérico. Hay tantos factores que entran en la seguridad de subprocesos que están fuera de la responsabilidad o el alcance de una clase de biblioteca / marco que afecta la capacidad para que sea realmente "seguro para subprocesos" ... Uno de los inconvenientes que ha señalado fuera es el rendimiento. Es imposible escribir una colección performante que también sea segura para subprocesos porque tiene que asumir lo peor ...
La práctica generalmente recomendada es utilizar la colección que desee y acceder a ella de forma segura para subprocesos. Esto es básicamente el motivo por el que no hay más colecciones seguras para subprocesos en el marco. Puede encontrar más información sobre esto en http://blogs.msdn.com/b/bclteam/archive/2005/03/15/396399.aspx#9534371
La razón por la que no existe tal estructura de datos es que todas las colecciones tienen un tiempo de operación de búsqueda de O(n)
. Estos son IndexOf
, Remove(element)
etc. Todos se enumeran a través de todos los elementos y verificando su igualdad.
Sólo las tablas hash tienen un tiempo de búsqueda de O (1). En el escenario concurrente, el tiempo de búsqueda O (n) llevaría a un bloqueo muy largo de una colección. Otros hilos no podrán agregar elementos durante este tiempo.
En el diccionario solo se bloqueará la celda afectada por hash. Otros subprocesos pueden continuar agregando mientras uno está comprobando la igualdad a través de elementos en la celda hash.
Mi consejo es seguir y usar ConcurrentDictionary.
Por cierto, tienes razón en que ConcurrentDictionary está un poco sobredimensionado para tu solución. Lo que realmente necesita es verificar rápidamente si un objeto está en el trabajo o no. Un HashSet
sería perfecto para eso. Básicamente no hace nada, entonces Add(element)
, Contains(element)
, Remove(element)
. Hay una implementación ConcurrentHeshSet
en java. Para c # encontré esto: cómo implementar ConcurrentHashSet en .Net no sé qué tan bueno es.
Como primer paso, todavía escribiría un contenedor con la interfaz HashSet
alrededor de ConcurrentDictionary
HashSet
en funcionamiento y luego probaría diferentes implementaciones y vería las diferencias de rendimiento.