Mecanismo de sincronización para un objeto observable.

Imaginemos que tenemos que sincronizar el acceso de lectura / escritura a los recursos compartidos. Múltiples hilos accederán a ese recurso tanto en lectura como en escritura (la mayoría de las veces para leer, a veces para escribir). Supongamos también que cada escritura siempre activará una operación de lectura (el objeto es observable).

Para este ejemplo, me imagino una clase como esta (perdone la sintaxis y el estilo, solo con fines ilustrativos):

class Container {
    public ObservableCollection<Operand> Operands;
    public ObservableCollection<Result> Results;
}

Estoy tentado a usar unReadWriterLockSlim para este propósito, además, lo pondría enContainer nivel (imagine que el objeto no es tan simple y una operación de lectura / escritura puede involucrar múltiples objetos):

public ReadWriterLockSlim Lock;

Implementación deOperand yResult No tiene ningún significado para este ejemplo. Ahora imaginemos un código que observa.Operands y producirá un resultado para poner enResults:

void AddNewOperand(Operand operand) {
    try {
        _container.Lock.EnterWriteLock();
        _container.Operands.Add(operand);
    }
    finally {
        _container.ExitReadLock();
    }
}

Nuestro observador hipotético hará algo similar pero consumirá un nuevo elemento y se bloqueará conEnterReadLock() para obtener operandos y luegoEnterWriteLock() para agregar resultado (déjame omitir código para esto). Esto producirá una excepción debido a la recursión, pero si configuroLockRecursionPolicy.SupportsRecursion entonces solo abriré mi código de bloqueo (desdeMSDN):

De forma predeterminada, las nuevas instancias de ReaderWriterLockSlim se crean con el indicador LockRecursionPolicy.NoRecursion y no permiten la recursión. Esta política predeterminada se recomienda para todos los nuevos desarrollos, porquerecursion introduce complicaciones innecesarias yhace que su código sea más propenso a los interbloqueos.

Repito parte relevante para mayor claridad:

La recursión [...] hace que su código sea más propenso a los interbloqueos.

Si no me equivoco conLockRecursionPolicy.SupportsRecursion Si desde el mismo hilo pido un, digamos, lea bloqueo entoncesalguien de lo contrario, pide un bloqueo de escritura, entonces tendré un bloqueo de seguridad, entonces lo que dice MSDN tiene sentido. Además, la recursión degradará el rendimiento también de manera mensurable (y no es lo que quiero si estoy usandoReadWriterLockSlim en lugar deReadWriterLock oMonitor).

Pregunta (s)

Finalmente, mis preguntas son (tenga en cuenta que no estoy buscando una discusión acerca de los mecanismos generales de sincronización, sabría para qué está maleste escenario productor / observable / observador):

¿Qué es mejor en esta situación? Para evitarReadWriterLockSlim a favor deMonitor (incluso si en el mundo real las lecturas de código serán mucho más que escrituras)?¿Abandonar con tan gruesa sincronización? Esto puede incluso producir un mejor rendimiento, pero hará que el código sea mucho más complicado (por supuesto, no en este ejemplo sino en el mundo real).¿Debo hacer notificaciones (de la colección observada) de forma asíncrona?¿Algo más que no pueda ver?

Sé que no hay unmejor mecanismo de sincronización por loherramienta debemos usar el correcto para nuestro caso, pero me pregunto si hay alguna práctica recomendada o simplemente ignoro algo muy importante entre los hilos y los observadores (imagínese usarloExtensiones reactivas de Microsoft pero la pregunta es general, no vinculada a ese marco).

¿Soluciones posibles?

Lo que intentaría es hacer que los eventos (de alguna manera) sean diferidos:

1ª solución
Cada cambio no dispararáCollectionChanged evento, se mantiene en una cola. Cuando el proveedor (el objeto que empuja datos) haya finalizado, forzará manualmente la cola para que se vacíe (elevando cada evento en secuencia). Esto puede hacerse en otro hilo o incluso en el hilo de la persona que llama (pero fuera del bloqueo).

Puede funcionar, pero hará que todo sea menos "automático" (cada notificación de cambio debe ser activada manualmente por el propio productor, más código para escribir, más errores por todos lados).

2da solución
Otra solución puede ser proporcionar una referencia a nuestrabloquear a la colección observable. Si me envuelvoReadWriterLockSlim en un objeto personalizado (útil para ocultarlo de una manera fácil de usarIDisposable objeto) puedo añadir unManualResetEvent para notificar que todos los bloqueos se han liberado de esta manera, la propia colección puede generar eventos (nuevamente en el mismo hilo o en otro hilo)

3ra solución
Otra idea podría ser simplemente hacer eventos asíncronos. Si el controlador de eventos necesitará un bloqueo, se detendrá para esperar su período de tiempo. Para esto me preocupa la cantidad de subprocesos grande que se puede usar (especialmente si se trata de un grupo de subprocesos).

Sinceramente, no sé si alguno de estos es aplicable en aplicaciones del mundo real (personalmente, desde el punto de vista de los usuarios, prefiero el segundo, pero implica una colección personalizada para todo y hace que la colección sea consciente de los hilos y lo evitaría, si posible). No me gustaría hacer el código más complicado de lo necesario.

Respuestas a la pregunta(3)

Su respuesta a la pregunta