.NET

29 abr, 2011

Sincronização de Threads em C#

Publicidade

Um aviso aos leitores deste artigo: boa parte do
conteúdo é uma tradução do artigo “Thread Synchronization (C# and
Visual Basic)”
disponibilizado pela Microsoft. Aproveite a leitura!

Um dos benefícios do uso de múltiplas threads em uma aplicação é que cada
uma delas
 é
executada
de forma assíncrona. Em aplicações desktop, isto
permite que
 tarefas
que consomem muito tempo
 possam ser executada em segundo plano enquanto a janela do aplicativo e os
controles
continuam
respondendo
. 

Para aplicações em servidores, múltiplas
tarefas
 fornecem a capacidade de lidar com
cada
 solicitação de entrada com uma thread diferente, caso contrário, cada novo pedido não será
atendido
 até que o pedido anterior
tenha
 completado.

No entanto, a natureza assíncrona de threads significa que o acesso a recursos compartilhados, como arquivos, conexões de rede e memória devem ser sincronizados. Caso contrário, duas ou mais threads podem acessar o mesmo recurso ao mesmo tempo, e cada um desconhece a atuação da outra ação. O
resultado
 é a corrupção de dados imprevisível e possíveis deadlocks.

Há algum tempo, fiz outros artigos que demonstram o uso de
threads em C# e Visual Basic. Você pode encontrá-los aqui:

Lock e SyncLock

O Lock (C#) e o SyncLock (VB.NET) são
declarações que podem ser utilizadas para garantir que um bloco de código seja
executado até sua conclusão, prevenindo a interrupção da execução por outras
threads.

Essas declarações são interpretadas pelo compilador como bloqueios
para um determinado objeto em um bloco de códigos. Se outra thread tenta
executar o código envolvido pelo lock/SyncLock, ela evai esperar até que a
primeira thread termine a execução e libere o objeto/código bloqueado.

//C#
public class LockTest
{
public void TestarLock()
{
lock (this)
{
// Código que estará protegido pelo lock.
}
}
}

Sincronização de eventos e Wait Handles

Existem dois tipos de sincronização: AutoResetEvent e
ManualResetEvent. Eles diferem apenas na medida em que as mudanças são setadas
automaticamento no AutoResetEvent e manualmente no ManualResetEvent. O
ManualResetEvent permite que qualquer número de threads possa ser alinhado e
ter seu estado atualizado.

Métodos são usados para esperar a execução de um ou mais
threads. Esses métodos indicam ao compilador quando e quantas threads devem ter
o processamento concluído para que o fluxo continue. São eles: WaitOne, WaitAny
e WaitAll.

WaitOne faz com que o compilador espere a execução de um único
thread estar concluída. WaitAny faz o compilador aguardar a execução dos
métodos indicados. WaitAll bloqueia a execução até que todas as threads estejam
concluídas. Para sinalizar a conclusão da execução de uma thread, usa-se o
método Set.

// C#
namespace GerenciaSincronizacao
{
public class GerenciarThreads
{
private ManualResetEvent[] manualResetEvent;

private void ExecutarThread0(object obj)
{
Thread.Sleep(20000);
Console.WriteLine("Thread 0 concluída!");
manualResetEvent[0].Set();
}

private void ExecutarThread1(object obj)
{
Thread.Sleep(13000);
Console.WriteLine("Thread 1 concluída!");
manualResetEvent[1].Set();
}

private void ExecutarThread2(object obj)
{
Thread.Sleep(9000);
Console.WriteLine("Thread 2 concluída!");
manualResetEvent[2].Set();
}

private void ExecutarThread3(object obj)
{
Thread.Sleep(17000);
Console.WriteLine("Thread 3 concluída!");
manualResetEvent[3].Set();
}

public void Executar()
{
manualResetEvent = new ManualResetEvent[4];

manualResetEvent[0] = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(this.ExecutarThread0);

manualResetEvent[1] = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(this.ExecutarThread1);

manualResetEvent[2] = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(this.ExecutarThread2);

manualResetEvent[3] = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(this.ExecutarThread3);

WaitHandle.WaitAll(manualResetEvent);
}
}

class Program
{
static void Main(string[] args)
{
GerenciarThreads gt = new GerenciarThreads();
gt.Executar();

Console.WriteLine("Pressione ENTER para terminar.");
Console.ReadKey();
}
}
}

A classe
Interlocked

Você pode usar os métodos da classe Interlocked para evitar
problemas que podem ocorrer quando várias threads tentam simultaneamente
atualizar ou comparar um mesmo valor. Os métodos dessa classe permitem que você
assegure o incremento, decremento, troca e comparação entre valores em qualquer
thread.

As operações mais utilizadas desta classe são:

  • O método Add adiciona um valor inteiro para
    uma variável e retorna o novo valor da variável.
  • O método Read lê um valor inteiro de 64 bits.
  • Os métodos Increment e Decrement são
    utilizados para incrementar ou decrementar valores em uma variável e retornam o
    valor resultante.

O
código a seguir demonstra o uso do Increment:

// C#
private static ManualResetEvent[] manualResetEvents;

public void Teste()
{
int intQuantidadeInteracoes = 10;
int intContador = 0;

manualResetEvents = new ManualResetEvent[intQuantidadeInteracoes];

for (int i = 0; i < intQuantidadeInteracoes; i++)
{
manualResetEvents[intContador] = new ManualResetEvent(false);

ThreadPool.QueueUserWorkItem(this.Executar, intContador);

Interlocked.Increment(ref intContador);
}

WaitHandle.WaitAll(manualResetEvents);
}

public void Executar(object intContador)
{
Console.WriteLine("Posição atual do contador: {0}",
Convert.ToInt32(intContador));

manualResetEvents[Convert.ToInt32(intContador)].Set();
}

Referências