Semaphore e SemaphoreSlim

A classe System.Threading.Semaphore representa um sinal com nome (em todo o sistema) ou local. É um wrapper fino em torno do objeto de sinal Win32. Sinais do Win32 são contagem de sinais que podem ser usadas para controlar o acesso a um pool de recursos.

A classe SemaphoreSlim representa um sinal leve e rápido que pode ser usado para aguardar por um único processo quando forem esperados tempos de espera muito curtos. SemaphoreSlim conta com o máximo possível em primitivos de sincronização fornecidos pelo Common Language Runtime (CLR). No entanto, fornece também identificadores de espera baseados no kernel inicializados lentamente, conforme necessário para dar suporte à espera de vários sinais. SemaphoreSlim oferece suporte também ao uso de tokens de cancelamento, mas não oferece suporte a sinais com nome ou ao uso de um identificador de espera para sincronização.

Gerenciando um recurso limitado

Os threads inserem o sinal chamando o método WaitOne, que é herdado da classe WaitHandle, no caso de um objeto System.Threading.Semaphore ou o método SemaphoreSlim.Wait ou SemaphoreSlim.WaitAsync, no caso de um objeto SemaphoreSlim. Quando a chamada é retornada, a contagem no sinal é reduzida. Quando um thread solicita entrada e a contagem é zero, o thread é bloqueado. À medida que os threads liberam o sinal chamando o método Semaphore.Release ou SemaphoreSlim.Release, os threads bloqueados têm permissão para entrar. Não há uma ordem garantida, como primeiro a entrar, primeiro a sair (PEPS) ou último a entrar, primeiro a sair (UEPS) para threads bloqueados para inserir o sinal.

Um thread pode inserir o sinal várias vezes chamando do objeto System.Threading.Semaphore o método WaitOne ou do objeto SemaphoreSlim o método Wait repetidas vezes. Para liberar o sinal, o thread pode chamar a sobrecarga do método Semaphore.Release() ou SemaphoreSlim.Release() o mesmo número de vezes ou chamar a sobrecarga do método Semaphore.Release(Int32) ou SemaphoreSlim.Release(Int32) e especificar o número de entradas a ser liberado.

Sinais e identidade do thread

Os dois tipos de sinais não impõem a identidade do thread em chamadas para os métodos WaitOne, Wait, Release e SemaphoreSlim.Release. Por exemplo, um cenário de uso comum para sinais envolve um thread de produtor e um thread de consumidor, com um thread sempre incrementando a contagem de sinais e o outro sempre diminuindo-a.

É responsabilidade do programador garantir que um thread não libere o sinal muitas vezes. Por exemplo, suponha que um sinal tenha uma contagem máxima de dois, e que o thread A e o thread B insiram o sinal. Se um erro de programação no thread B fizer com que ele chame Release duas vezes, ambas as chamadas serão bem-sucedidas. A contagem no sinal está completa e quando o thread A eventualmente chama Release, uma SemaphoreFullException é lançada.

Sinais com nome

O sistema operacional Windows permite que os sinais tenham nomes. Um sinal com nome é para todo o sistema. Ou seja, depois que o sinal com nome for criado, ele ficará visível para todos os threads em todos os processos. Assim, o sinal com nome pode ser usado para sincronizar as atividades de processos, bem como os threads.

Você pode criar um objeto Semaphore que representa um sinal de sistema com nome usando um dos construtores que especifica um nome.

Observação

Como os sinais com nome são para todo o sistema, é possível ter vários objetos Semaphore que representam o mesmo sinal com nome. Cada vez que você chamar um construtor ou o método Semaphore.OpenExisting, um novo objeto Semaphore é criado. Especificar o mesmo nome repetidas vezes cria vários objetos que representam o mesmo sinal com nome.

Tenha cuidado ao usar sinais com nome. Como eles são para todo o sistema, outro processo que usa o mesmo nome pode inserir seu sinal inesperadamente. Código mal-intencionado em execução no mesmo computador pode usar isso como base de um ataque de negação de serviço.

Use a segurança de controle de acesso para proteger um objeto Semaphore que representa um sinal com nome, preferencialmente usando um construtor que especifica um objeto System.Security.AccessControl.SemaphoreSecurity. Também é possível aplicar a segurança de controle de acesso usando o método Semaphore.SetAccessControl, mas isso deixa uma janela de vulnerabilidade entre o momento em que o sinal é criado e o momento em que ele é protegido. Proteger sinais com a segurança de controle de acesso ajuda a impedir ataques mal-intencionados, mas não resolve o problema de colisão de nome não intencional.

Confira também