Mecanismo de Sincronização para um Objeto Observável

Vamos imaginar que temos que sincronizar o acesso de leitura / gravação a recursos compartilhados. Vários segmentos acessarão esse recurso tanto em leitura quanto em escrita (na maioria das vezes para leitura, às vezes para gravação). Vamos supor também que cada gravação sempre acione uma operação de leitura (o objeto é observável).

Para este exemplo, vou imaginar uma classe como essa (perdoe sintaxe e estilo, é apenas para fins de ilustração):

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

Estou tentado a usar umReadWriterLockSlim para este fim, além disso, eu colocá-lo emContainer level (imaginar o objeto não é tão simples e uma operação de leitura / gravação pode envolver vários objetos):

public ReadWriterLockSlim Lock;

Implementação deOperand eResult não tem significado para este exemplo. Agora vamos imaginar algum código que observeOperands e irá produzir um resultado para colocar emResults:

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

Nosso observador hipotético fará algo semelhante, mas consumirá um novo elemento e trancará comEnterReadLock() para obter operandos e depoisEnterWriteLock() para adicionar resultado (deixe-me omitir código para isso). Isso produzirá uma exceção por causa da recursão, mas se eu definirLockRecursionPolicy.SupportsRecursion então eu só vou abrir meu código para dead-locks (deMSDN):

Por padrão, novas instâncias de ReaderWriterLockSlim são criadas com o sinalizador LockRecursionPolicy.NoRecursion e não permitem a recursão. Esta política padrão é recomendada para todos os novos desenvolvimentos, porquerecursão introduz complicações desnecessárias etorna seu código mais propenso a deadlocks.

Repito parte relevante para maior clareza:

A recursão [...] torna seu código mais propenso a deadlocks.

Se eu não estiver errado comLockRecursionPolicy.SupportsRecursion se da mesma discussão eu peço um, digamos, leia o bloqueio, em seguida,alguém else pede um bloqueio de gravação, então eu vou ter um dead-lock, então o que o MSDN diz faz sentido. Além disso, a recursão também degradará o desempenho de forma mensurável (e não é o que eu quero se eu estiver usandoReadWriterLockSlim ao invés deReadWriterLock ouMonitor).

Questões)

Finalmente minhas perguntas são (por favor note que eu não estou procurando por uma discussão sobre mecanismos gerais de sincronização, eu sei o que está erradoeste cenário produtor / observável / observador):

O que há de melhor nessa situação? EvitarReadWriterLockSlim A favor deMonitor (mesmo que no mundo real as leituras de código sejam muito mais do que escritas)?Desistir de uma sincronização tão grosseira? Isso pode até produzir melhor desempenho, mas tornará o código muito mais complicado (claro que não neste exemplo, mas no mundo real).Devo apenas tornar as notificações (da coleção observada) assíncronas?Algo mais que não posso ver?

Eu sei que não existemelhor mecanismo de sincronização tãoferramenta nós usamos deve ser um direito para o nosso caso, mas eu me pergunto se há alguma prática recomendada ou eu simplesmente ignorar algo muito importante entre tópicos e observadores (imagine usarExtensões reativas Microsoft mas a questão é geral, não vinculada a essa estrutura).

Soluções possíveis?

O que eu tentaria fazer com que os eventos (de alguma forma) fossem adiados:

1a solução
Cada alteração não irá dispararCollectionChanged evento, é mantido em uma fila. Quando o provedor (objeto que envia dados) tiver terminado, ele forçará manualmente a fila a ser limpa (levantando cada evento em seqüência). Isso pode ser feito em outro thread ou até mesmo no encadeamento do chamador (mas fora do bloqueio).

Isso pode funcionar, mas tornará tudo menos "automático" (cada notificação de alteração deve ser acionada manualmente pelo próprio produtor, mais código para escrever, mais bugs ao redor).

Segunda solução
Outra solução pode ser fornecer uma referência ao nossotrancar para a coleção observável. Se eu embrulharReadWriterLockSlim em um objeto personalizado (útil para escondê-lo em um fácil de usarIDisposable objeto) Eu posso adicionar umManualResetEvent para notificar que todos os bloqueios foram liberados dessa forma, a própria coleção pode gerar eventos (novamente no mesmo encadeamento ou em outro encadeamento).

3a solução
Outra ideia poderia ser apenas tornar os eventos assíncronos. Se o manipulador de eventos precisar de um bloqueio, ele será interrompido para aguardar o seu período de tempo. Por isso, eu me preocupo com a grande quantidade de threads que pode ser usada (especialmente se for do pool de threads).

Honestamente, eu não sei se algum deles é aplicável na aplicação do mundo real (pessoalmente - do ponto de vista dos usuários - eu prefiro o segundo mas implica coleção customizada para tudo e torna a coleção ciente do threading e eu o evitaria, se possível). Eu não gostaria de tornar o código mais complicado do que o necessário.

questionAnswers(3)

yourAnswerToTheQuestion