2008-10-17 20 views
6

Tengo una clase personalizada que implementa ICollection, y esta clase es de solo lectura, es decir. IsReadOnly devuelve verdadero (en lugar de usar la palabra clave readonly), y todas las funciones que normalmente modificarían los datos en la colección arrojan InvalidOperationException.ICollection, colecciones de solo lectura y sincronización. ¿Es esto correcto?

Ahora, teniendo en cuenta tal construcción, y una rápida revisión de los problemas de seguridad del hilo al implementar ICollection (específicamente ICollection.IsSynchronized y amigos), se me ocurrió esta solución rápida y sucia.

bool ICollection.IsSynchronised { get{ return true; } } 
object ICollection.SyncRoot { get{ return new Object(); } } 

Ahora, teniendo en cuenta los ejemplos de MSDN, esto no va a causar diferentes hilos para bloquear correctamente, debido a que están recibiendo diferentes objetos de SyncRoot. Sin embargo, dado que esta es una colección de solo lectura, ¿es esto un problema? ¿Hay problemas de memoria/GC con la devolución de new Object()? ¿Algún otro problema que pueda ver con esta implementación?

Respuesta

4

Sí, esto es un problema en algunos casos. Aunque la colección es de solo lectura y no se puede cambiar, los objetos a los que hace referencia la colección no son de solo lectura. Por lo tanto, si los clientes usan SyncRoot para realizar el bloqueo, no serán seguros para la ejecución de subprocesos al modificar los objetos a los que hace referencia la colección.

recomendaría añadiendo:

private readonly object syncRoot = new object(); 

a su clase. Devuélvelo como SyncRoot y listo.

1

Supongo que el problema sería si los clientes utilizan su raíz de sincronización para lograr el bloqueo no solo de su colección, sino de otra cosa. Suponiendo que almacenan en caché el tamaño de la colección, o quizás "qué subconjunto de esta colección coincide con un predicado", supondrían razonablemente que podrían usar su SyncRoot para proteger tanto a su colección como a su otro miembro.

Personalmente, casi no uso SyncRoot, pero creo que sería sensato devolver siempre lo mismo.

+0

La colección es, de hecho, inmutable, no hay forma de modificarla (excepto para cavar en la reflexión, por supuesto), por lo que todo este tipo de cosas deberían estar garantizadas, ¿no? –

+0

Considere que sus clientes pueden no saber que están obteniendo una colección inmutable. Obviamente, no están tratando de modificarlo (o ya obtendrán un error) pero es posible que se estén bloqueando para asegurarse de que no fallen si se les proporciona una colección mutable. –

+0

Sí, de acuerdo, llaman a Syncroot, obtienen un objeto para bloquear, un objeto que nadie más obtendrá, por lo que el bloqueo es inútil. Pero, ¿deberíamos realmente sostener otros hilos innecesariamente? –

2

Parece muy extraño devolver un objeto diferente cada vez ... de hecho, muy raramente (si alguna vez) utilizo el enfoque SyncRoot, ya que a menudo necesito sincronizar entre varios objetos, etc., por lo que un objeto separado es más significativo .

Pero si los datos son realmente inmutables (solo lectura), ¿por qué no devuelve falso de IsSynchronized?

Re GC: cualquiera de estos objetos suele ser de corta duración y recogerse en GEN0. Si tiene un campo con un objeto (para el candado), duraría tanto como la colección, pero lo más probable es que no duela de todos modos ...

Si necesita un candado, me sentiría tentado a simplemente una:

private readonly object lockObj = new object(); 

también es posible usar un enfoque perezoso para solamente "nuevo" cuando sea necesario, lo que hace que una cierta cantidad de sentido si en realidad no espera a nadie para pedir la sincronización de frenos (devuelve falso a IsSynchronized).

Otro enfoque común es devolver "esto"; mantiene las cosas simples, pero los riesgos entran en conflicto con algún otro código usando su objeto como un bloqueo para un propósito no relacionado. Raro, pero posible. Este es realmente el enfoque que [MethodImpl] utiliza para sincronizar.

+1

¿No estaría diciendo que el objeto no es seguro para subprocesos, cuando de hecho lo es? –

+0

Quizás; OMI, toda la lógica de las colecciones sincronizadas está un poco remendada: es raro que desee seguridad de subprocesos para una operación * simple *; normalmente desea verificar un valor y luego hacer otra cosa. Sospecho que el código más común nunca revisará esta propiedad de todos modos ... –

+0

Creo que la razón principal para esto es poner una instrucción bloqueada alrededor de bucles foreach, por lo que el bucle no se equivoca si alguien modifica la colección mientras estás enumerando sobre eso. –

Cuestiones relacionadas