Pergunta

Recentemente, tenho refatorado parte do meu código C # e descobri que algumas práticas de bloqueio foram verificadas novamente. Eu não sabia que era uma prática ruim na época e eu realmente quero me livrar disso.

O problema é que eu tenho uma classe que deve ser inicializada lentamente e freqüentemente acessada por vários threads. Eu também não quero mover a inicialização para um inicializador estático, porque estou planejando usar uma referência fraca para evitar que o objeto inicializado permaneça muito tempo na memória. No entanto, se necessário, quero 'reviver' o objeto garantindo que isso aconteça de maneira segura para thread.

Eu queria saber se usar um ReaderWriterLockSlim em C # e inserir um UpgradeableReadLock antes da primeira verificação e, se necessário, inserir um bloqueio de gravação para a inicialização seria uma solução aceitável. Aqui está o que estou tendo em mente:

public class LazyInitialized
{
    private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();

    private volatile WeakReference _valueReference = new WeakReference(null);
    public MyType Value
    {
        get
        {
            MyType value = _valueReference.Target as MyType;
            _lock.EnterUpgradeableReadLock();
            try
            {
                if (!_valueReference.IsAlive) // needs initializing
                {
                    _lock.EnterWriteLock();
                    try
                    {
                        if (!_valueReference.IsAlive) // check again
                        {
                            // prevent reading the old weak reference
                            Thread.MemoryBarrier(); 
                            _valueReference = new WeakReference(value = InitializeMyType());
                        }
                    }
                    finally
                    {
                        _lock.ExitWriteLock();
                    }
                }
            }
            finally
            {
                _lock.ExitUpgradeableReadLock();
            }
            return value;
        }       
    }

    private MyType InitializeMyType()
    {
        // code not shown    
    }
}

Meu ponto é que nenhum outro thread deve tentar inicializar o item mais uma vez, enquanto muitos threads devem ler simultaneamente uma vez que o valor é inicializado. O bloqueio de leitura atualizável deve bloquear todos os leitores se o bloqueio de gravação for adquirido, portanto, enquanto o objeto está sendo inicializado, o comportamento será semelhante a ter uma instrução de bloqueio onde o bloqueio de leitura atualizável começa. Após a inicialização, o bloqueio de leitura atualizável permitirá vários encadeamentos, portanto, o desempenho de espera de cada encadeamento não estará presente.

Eu também li um artigo aqui dizendo que volátil faz com que barreiras de memória sejam inseridas automaticamente antes da leitura e depois da gravação, portanto, presumo que apenas uma barreira definida manualmente entre a leitura e a gravação será suficiente para garantir que o objeto _valueReference seja lido corretamente. Terei prazer em receber seus conselhos e críticas por usar essa abordagem.

Foi útil?

Solução

Para enfatizar o ponto que @Mannimarco faz: se este é o único ponto de acesso ao Value, e parece que sim, então toda a configuração do ReaderWriterLockSlim não é melhor do que uma abordagem simples de Monitor.Enter / Monitor.Leave.Porém, é muito mais complicado.

Portanto, acredito que o código a seguir é equivalente em função e eficiência:

private WeakReference _valueReference = new WeakReference(null);
private object _locker = new object();

public MyType Value
{    
  get
  {    
    lock(_locker)  // also provides the barriers
    {
        value = _valueReference.Target;

        if (!_valueReference.IsAlive)
        {
            _valueReference = new WeakReference(value = InitializeMyType());
        }
        return value; 
    }
  }    
}

Outras dicas

Um aviso: apenas um único thread pode entrar no modo UpgradeableReadLock por vez.Verifique ReaderWriterLockSlim .Portanto, se os threads se acumulam enquanto o primeiro thread entra no modo de gravação e cria o objeto, você terá um gargalo até que o backup seja (com sorte) resolvido.Eu sugeriria seriamente o uso de um inicializador estático, isso tornará sua vida mais fácil.

EDIT: Dependendo da frequência com que o objeto precisa ser recriado, eu realmente sugeriria o uso da classe Monitor e seus métodos Wait e Pulse.Se o valor precisar ser recriado, faça com que os threads Esperem em um objeto e Pulse outro objeto para permitir que um thread de trabalho saiba que precisa ser ativado e criar um novo objeto.Uma vez que o objeto foi criado, PulseAll permitirá que todos os threads do leitor sejam ativados e capturem o novo valor.(em teoria)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top