Sincronizando dados para multithreading
Quando vários threads podem fazer chamadas para as propriedades e métodos de um único objeto, é fundamental que essas chamadas sejam sincronizadas. Caso contrário, um thread pode interromper o que outro thread está fazendo e o objeto pode ser deixado em um estado inválido. Uma classe cujos membros são protegidos de tais interrupções é denominada thread-safe.
O .NET fornece várias estratégias para sincronizar o acesso à instância e aos membros estáticos:
Regiões de código sincronizadas. Você pode usar o suporte de compilador ou classe Monitor para esta classe para sincronizar apenas o bloco de código que precisa, melhorando o desempenho.
Sincronização manual. Você pode usar os objetos de sincronização fornecidos pela biblioteca de classes do .NET. Confira Visão geral dos primitivos de sincronização, que inclui uma discussão sobre a classe Monitor.
Contextos sincronizados. Para aplicativos do .NET Framework e Xamarin apenas, você pode usar o SynchronizationAttribute para habilitar a sincronização simples e automática para objetos ContextBoundObject.
Classes de coleção no namespace System.Collections.Concurrent. Essas classes fornecem operações sincronizadas de adição e remoção internas. Para obter mais informações, veja Coleções thread-safe.
O Common Language Runtime fornece um modelo de thread em que as classes se enquadram em uma série de categorias que podem ser sincronizadas de diferentes maneiras, dependendo dos requisitos. A tabela a seguir mostra o suporte de sincronização fornecido para campos e métodos com uma determinada categoria de sincronização.
Categoria | Campos globais | Campos estáticos | Métodos estáticos | Campos de instância | Métodos de instância | Blocos de código específico |
---|---|---|---|---|---|---|
Sem sincronização | No | No | No | No | No | No |
Contexto sincronizado | No | No | No | Sim | Sim | No |
Regiões de código sincronizadas | No | No | Somente se marcado | No | Somente se marcado | Somente se marcado |
Sincronização manual | Manual | Manual | Manual | Manual | Manual | Manual |
Sem sincronização
Este é o padrão para objetos. Qualquer thread pode acessar qualquer método ou campo a qualquer momento. Apenas um thread por vez deve acessar esses objetos.
Sincronização manual
A biblioteca de classes do .NET fornece uma série de classes para sincronizar threads. Confira Visão geral dos primitivos de sincronização.
Regiões de código sincronizadas
Você pode usar a classe Monitor ou uma palavra-chave do compilador para sincronizar blocos de código, métodos de instância e métodos estáticos. Não há suporte para campos estáticos sincronizados.
Tanto o Visual Basic quanto o C # dão suporte à marcação de blocos de código com uma palavra-chave de idioma específica, a instrução lock
em C # ou a instrução SyncLock
no Visual Basic. Quando o código é executado por um thread, uma tentativa é feita para adquirir o bloqueio. Se o bloqueio já foi adquirido por outro thread, o thread bloqueia até que o bloqueio fique disponível. Quando o thread sai do bloco de código sincronizado, o bloqueio é liberado, não importa como o thread sai do bloco.
Observação
A partir do C# 13, a instrução lock
reconhece se o objeto bloqueado é uma instância de System.Threading.Lock e usa o método EnterScope
para criar uma região sincronizada. O lock
, quando o destino não é uma instância Lock
, e as instruções SyncLock
são implementados usando Monitor.Enter e Monitor.Exit, portanto, outros métodos de Monitor podem ser usados em conjunto com eles dentro da região sincronizada.
Você também pode decorar um método com um MethodImplAttribute com um valor de MethodImplOptions.Synchronized, que tem o mesmo efeito de usar Monitor ou uma das palavras-chave do compilador para bloquear todo o corpo do método.
Thread.Interrupt pode ser usado para interromper um thread fora das operações de bloqueio, como aguardar o acesso a uma região de código sincronizada. Thread.Interrupt também é usado para interromper threads fora das operações, como Thread.Sleep.
Importante
Não bloqueie o tipo — isto é, typeof(MyType)
no C#, GetType(MyType)
no Visual Basic, ou MyType::typeid
no C++ — para proteger métodos static
(métodos Shared
no Visual Basic). Use um objeto estático privado em vez disso. Da mesma forma, não use this
no C # (Me
no Visual Basic) para bloquear métodos de instância. Use um objeto privado em vez disso. Uma classe ou instância pode ser bloqueada por código diferente do seu, potencialmente causando deadlocks ou problemas de desempenho.
Suporte de compilador
O Visual Basic e o C# dão suporte a uma palavra-chave de idioma que usa Monitor.Enter e Monitor.Exit para bloquear o objeto. O Visual Basic oferece suporte à instrução SyncLock; C# oferece suporte à instrução lock.
Em ambos os casos, se uma exceção for lançada no bloqueio de código, o bloqueio adquirido por lock ou SyncLock é liberado automaticamente. Os compiladores C # e Visual Basic emitem um bloqueio try/finally com Monitor.Enter no início da tentativa, e Monitor.Exit no bloqueio finally. Se uma exceção for lançada dentro do bloqueio lock ou SyncLock, o manipulador finally é executado para permitir que você faça qualquer trabalho de limpeza.
Contexto sincronizado
Nos aplicativos .NET Framework e Xamarin somente, você pode usar o SynchronizationAttribute em qualquer ContextBoundObject para sincronizar todos os campos e métodos de instância. Todos os objetos no mesmo domínio de contexto compartilham o mesmo bloqueio. Múltiplos threads podem acessar os métodos e os campos, mas somente um único thread é permitido em qualquer momento.