thread safe example concurrentbag concurrent collection c# concurrency

c# - safe - ¿Por qué ConcurrentBag<T> no implementa ICollection<T>?



concurrentdictionary c# example (4)

Tengo un método que toma un IList <> y le agrega cosas. Me gustaría pasarle un ConcurrentBag en algunos casos, pero no implementa IList <> o ICollection <>, solo el ICollection no genérico, que no tiene un método Add.

Ahora, veo por qué no puede (tal vez) implementar IList - no es una colección ordenada por lo que no tiene sentido que tenga un indexador. Pero no veo un problema con ninguno de los métodos de ICollection<> .

¿Entonces por qué? Y también: ¿existe una colección segura para subprocesos en .NET que implemente interfaces más robustas?


...

using System.Linq; bool result = MyConcurrentBag.Contains("Item");

Dando un tipo de capacidad de ICollection.



No es que ConcurrentBag<T> no haya podido implementar ICollection<T> ; probablemente pueda imaginar que Contains podría implementarse utilizando TryPeek o Remove with TryTake .

El problema es que tratar a un ConcurrentBag<T> como un ICollection<T> (por ejemplo, al permitir una conversión implícita al pasar un ConcurrentBag<T> a un método que solo toma ICollection<T> ) sería imprudente, porque la mayoría de los consumidores de ICollection<T> espera que tenga una semántica dramáticamente diferente de ConcurrentBag<T> .

La mayoría de los métodos que toman una ICollection<T> como un parámetro pueden hacer suposiciones (que son seguras en un escenario de un solo hilo) como " Add seguido de Contains siempre devolverá true ", o "si Contains devuelve true , también lo hará" Remove ". Sin embargo, en situaciones altamente multihilo (que es donde es probable que uno use ConcurrentBag<T> en primer lugar), es muy poco probable que estas suposiciones se cumplan. Esto podría exponer errores en el código que se escribió con el supuesto de usar ICollection<T> en un escenario de un solo hilo.

Si realmente necesita exponer ConcurrentBag<T> como ICollection<T> (y sabe que el código al que lo está pasando está esperando que funcione de una manera que no sea ICollection<T> ), debería ser bastante simple para escribir una clase de envoltorio (que usa el patrón de adaptador ) para simular los métodos de ICollection<T> utilizando los métodos disponibles más cercanos en ConcurrentBag<T> .


Una List<T> no es concurrente y, por lo tanto, puede implementar ICollection<T> que le da el par de métodos Contains y Add . Si Contains devuelve false , puede llamar a Add manera segura sabiendo que tendrá éxito.

Un ConcurrentBag<T> es concurrente y, por lo tanto, no puede implementar ICollection<T> porque la respuesta Contains devoluciones podría no ser válida en el momento en que llame a Add . En su lugar, implementa IProducerConsumerCollection<T> que proporciona el método único TryAdd que realiza el trabajo de Contains y Add .

Desafortunadamente, desea operar en dos cosas que son colecciones pero no comparten una interfaz común. Hay muchas maneras de resolver este problema, pero mi enfoque preferido cuando la API es tan similar como esta es proporcionar sobrecargas de métodos para ambas interfaces y luego usar expresiones lambda para crear delegados que realizan la misma operación para cada interfaz usando sus propios métodos. Luego puede usar ese delegado en lugar de donde hubiera realizado la operación casi común.

Aquí hay un ejemplo simple:

public class Processor { /// <summary> /// Process a traditional collection. /// </summary> /// <param name="collection">The collection.</param> public void Process(ICollection<string> collection) { Process(item => { if (collection.Contains(item)) return false; collection.Add(item); return true; }); } /// <summary> /// Process a concurrent collection. /// </summary> /// <param name="collection">The collection.</param> public void Process(IProducerConsumerCollection<string> collection) { Process(item => collection.TryAdd(item)); } /// <summary> /// Common processing. /// </summary> /// <param name="addFunc">A func to add the item to a collection</param> private void Process(Func<string, bool> addFunc) { var item = "new item"; if (!addFunc(item)) throw new InvalidOperationException("duplicate item"); } }