Compartilhar via


Classe System.Threading.Monitor

Este artigo fornece comentários complementares à documentação de referência para esta API.

A classe Monitor permite que você sincronize o acesso a uma região de código ao tomar e liberar um bloqueio em um objeto específico, chamando os métodos Monitor.Enter, Monitor.TryEnter e Monitor.Exit. Os bloqueios de objeto fornecem a capacidade de restringir o acesso a um bloco de código, normalmente chamado de seção crítica. Enquanto um thread possui o bloqueio de um objeto, nenhum outro thread pode adquirir esse bloqueio. Você também pode usar a Monitor classe para garantir que nenhum outro thread tenha permissão para acessar uma seção do código do aplicativo que está sendo executada pelo proprietário do bloqueio, a menos que o outro thread esteja executando o código usando um objeto bloqueado diferente. Porque a classe Monitor tem afinidade de thread, o thread que adquiriu um bloqueio deve liberar o bloqueio chamando o método Monitor.Exit.

Visão geral

Monitor tem os seguintes recursos:

  • Ele está associado a um objeto sob demanda.
  • Ele não está associado, o que significa que pode ser chamado diretamente de qualquer contexto.
  • Uma instância da Monitor classe não pode ser criada; os métodos da Monitor classe são todos estáticos. Cada método é passado pelo objeto sincronizado que controla o acesso à seção crítica.

Observação

Use a Monitor classe para bloquear objetos que não sejam cadeias de caracteres, isto é, outros tipos de referência que não sejam String, e não tipos de valor. Para obter detalhes, consulte as sobrecargas do método Enter e a seção O objeto de bloqueio mais adiante neste artigo.

A tabela a seguir descreve as ações que podem ser executadas por threads que acessam objetos sincronizados:

Ação Descrição
Enter, TryEnter Adquire um bloqueio para um objeto. Essa ação também marca o início de uma seção crítica. Nenhum outro thread pode entrar na seção crítica, a menos que esteja executando as instruções na seção crítica usando um objeto bloqueado diferente.
Wait Libera o bloqueio em um objeto para permitir que outros threads bloqueiem e acessem o objeto. O thread de chamada aguarda enquanto outro thread acessa o objeto. Sinais de pulso são usados para notificar threads de espera sobre alterações no estado de um objeto.
Pulse (sinal), PulseAll Envia um sinal para um ou mais threads de espera. O sinal notifica um thread de espera que o estado do objeto bloqueado foi alterado e o proprietário do bloqueio está pronto para liberar o bloqueio. O thread de espera é colocado na fila pronta do objeto para que eventualmente possa receber o bloqueio do objeto. Depois que o thread tiver o bloqueio, ele poderá verificar o novo estado do objeto para ver se o estado desejado foi atingido.
Exit Libera o bloqueio em um objeto. Essa ação também marca o fim de uma seção crítica protegida pelo objeto bloqueado.

Há dois conjuntos de sobrecargas para os métodos Enter e TryEnter. Um conjunto de sobrecargas tem um parâmetro ref (em C#) ou ByRef (no Visual Basic) Boolean definido atomicamente como true se o bloqueio for adquirido, mesmo que uma exceção seja gerada ao adquirir o bloqueio. Utilize essas sobrecargas se for essencial liberar o bloqueio em todos os casos, mesmo quando os recursos que o bloqueio está protegendo podem não estar em um estado consistente.

O objeto de bloqueio

A classe Monitor consiste em métodos static (Shared no Visual Basic) que operam em um objeto que controla o acesso à seção crítica. As seguintes informações são mantidas para cada objeto sincronizado:

  • Uma referência ao thread que atualmente contém o bloqueio.
  • Uma referência a uma fila pronta, que contém os threads que estão prontos para obter o bloqueio.
  • Uma referência a uma fila de espera, que contém os threads que estão aguardando a notificação de uma alteração no estado do objeto bloqueado.

Monitor bloqueia objetos (ou seja, tipos de referência), não tipos de valor. Embora você possa transmitir um tipo de valor para Enter e Exit, ele é isolado separadamente para cada chamada. Como cada chamada cria um objeto separado, Enter nunca bloqueia e o código que ele supostamente está protegendo não é realmente sincronizado. Além disso, o objeto passado para Exit é diferente do objeto passado para Enter, portanto Monitor lança uma exceção SynchronizationLockException com a mensagem "O método de sincronização de objetos foi chamado de um bloco de código não sincronizado".

O exemplo a seguir ilustra esse problema. Ele inicia dez tarefas, cada uma das quais simplesmente dorme por 250 milissegundos. Cada tarefa atualiza uma variável de contador, nTasksque se destina a contar o número de tarefas que realmente foram iniciadas e executadas. Como nTasks é uma variável global que pode ser atualizada por várias tarefas simultaneamente, um monitor é usado para protegê-la contra modificações simultâneas por várias tarefas. No entanto, como mostra a saída do exemplo, cada uma das tarefas gera uma SynchronizationLockException exceção.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example1
{
    public static void Main()
    {
        int nTasks = 0;
        List<Task> tasks = new List<Task>();

        try
        {
            for (int ctr = 0; ctr < 10; ctr++)
                tasks.Add(Task.Run(() =>
                { // Instead of doing some work, just sleep.
                    Thread.Sleep(250);
                    // Increment the number of tasks.
                    Monitor.Enter(nTasks);
                    try
                    {
                        nTasks += 1;
                    }
                    finally
                    {
                        Monitor.Exit(nTasks);
                    }
                }));
            Task.WaitAll(tasks.ToArray());
            Console.WriteLine($"{nTasks} tasks started and executed.");
        }
        catch (AggregateException e)
        {
            String msg = String.Empty;
            foreach (var ie in e.InnerExceptions)
            {
                Console.WriteLine($"{ie.GetType().Name}");
                if (!msg.Contains(ie.Message))
                    msg += ie.Message + Environment.NewLine;
            }
            Console.WriteLine("\nException Message(s):");
            Console.WriteLine(msg);
        }
    }
}
// The example displays the following output:
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//    SynchronizationLockException
//
//    Exception Message(s):
//    Object synchronization method was called from an unsynchronized block of code.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Module Example3
    Public Sub Main()
        Dim nTasks As Integer = 0
        Dim tasks As New List(Of Task)()

        Try
            For ctr As Integer = 0 To 9
                tasks.Add(Task.Run(Sub()
                                       ' Instead of doing some work, just sleep.
                                       Thread.Sleep(250)
                                       ' Increment the number of tasks.
                                       Monitor.Enter(nTasks)
                                       Try
                                           nTasks += 1
                                       Finally
                                           Monitor.Exit(nTasks)
                                       End Try
                                   End Sub))
            Next
            Task.WaitAll(tasks.ToArray())
            Console.WriteLine("{0} tasks started and executed.", nTasks)
        Catch e As AggregateException
            Dim msg As String = String.Empty
            For Each ie In e.InnerExceptions
                Console.WriteLine("{0}", ie.GetType().Name)
                If Not msg.Contains(ie.Message) Then
                    msg += ie.Message + Environment.NewLine
                End If
            Next
            Console.WriteLine(vbCrLf + "Exception Message(s):")
            Console.WriteLine(msg)
        End Try
    End Sub
End Module
' The example displays the following output:
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'    SynchronizationLockException
'
'    Exception Message(s):
'    Object synchronization method was called from an unsynchronized block of code.

Cada tarefa gera uma exceção SynchronizationLockException porque a variável nTasks é isolada antes da chamada para o método Monitor.Enter em cada tarefa. Em outras palavras, cada chamada de método é passada a uma variável separada que é independente das outras. nTasks é novamente isolado na chamada para o método Monitor.Exit. Mais uma vez, isso cria dez novas variáveis em caixa, que são independentes umas das outras, nTaskse as dez variáveis em caixa criadas na chamada para o Monitor.Enter método. A exceção é gerada, então, porque nosso código está tentando liberar um bloqueio em uma variável recém-criada que não foi bloqueada anteriormente.

Embora você possa isolar uma variável de tipo de valor antes de chamar Enter e Exit, conforme mostrado no exemplo a seguir, e transmitir o mesmo objeto isolado para os dois métodos, não há vantagem em fazer isso. As alterações na variável solta não são refletidas na cópia isolada e não há como alterar o valor da cópia isolada.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {

      int nTasks = 0;
      object o = nTasks;
      List<Task> tasks = new List<Task>();

      try {
         for (int ctr = 0; ctr < 10; ctr++)
            tasks.Add(Task.Run( () => { // Instead of doing some work, just sleep.
                                        Thread.Sleep(250);
                                        // Increment the number of tasks.
                                        Monitor.Enter(o);
                                        try {
                                           nTasks++;
                                        }
                                        finally {
                                           Monitor.Exit(o);
                                        }
                                      } ));
         Task.WaitAll(tasks.ToArray());
         Console.WriteLine($"{nTasks} tasks started and executed.");
      }
      catch (AggregateException e) {
         String msg = String.Empty;
         foreach (var ie in e.InnerExceptions) {
            Console.WriteLine($"{ie.GetType().Name}");
            if (! msg.Contains(ie.Message))
               msg += ie.Message + Environment.NewLine;
         }
         Console.WriteLine("\nException Message(s):");
         Console.WriteLine(msg);
      }
   }
}
// The example displays the following output:
//        10 tasks started and executed.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Module Example2
    Public Sub Main()
        Dim nTasks As Integer = 0
        Dim o As Object = nTasks
        Dim tasks As New List(Of Task)()

        Try
            For ctr As Integer = 0 To 9
                tasks.Add(Task.Run(Sub()
                                       ' Instead of doing some work, just sleep.
                                       Thread.Sleep(250)
                                       ' Increment the number of tasks.
                                       Monitor.Enter(o)
                                       Try
                                           nTasks += 1
                                       Finally
                                           Monitor.Exit(o)
                                       End Try
                                   End Sub))
            Next
            Task.WaitAll(tasks.ToArray())
            Console.WriteLine("{0} tasks started and executed.", nTasks)
        Catch e As AggregateException
            Dim msg As String = String.Empty
            For Each ie In e.InnerExceptions
                Console.WriteLine("{0}", ie.GetType().Name)
                If Not msg.Contains(ie.Message) Then
                    msg += ie.Message + Environment.NewLine
                End If
            Next
            Console.WriteLine(vbCrLf + "Exception Message(s):")
            Console.WriteLine(msg)
        End Try
    End Sub
End Module
' The example displays the following output:
'       10 tasks started and executed.

Ao selecionar um objeto no qual sincronizar, você deve bloquear somente em objetos que sejam privados ou internos. O bloqueio em objetos externos pode resultar em deadlocks, pois o código não relacionado pode escolher os mesmos objetos para bloquear para fins diferentes.

Observe que você pode sincronizar um objeto em vários domínios de aplicação se o objeto usado para o bloqueio deriver de MarshalByRefObject.

A seção crítica

Use os Enter métodos e Exit para marcar o início e o fim de uma seção crítica.

Observação

Os métodos Enter e Exit fornecem funcionalidade idêntica à instrução lock em C# e à instrução SyncLock no Visual Basic, exceto pelas construções de linguagem que encapsulam a sobrecarga do método Monitor.Enter(Object, Boolean) e o método Monitor.Exit em um try... Bloqueio finally para garantir que o monitor seja liberado.

Se a seção crítica for um conjunto de instruções contíguas, o bloqueio adquirido pelo Enter método garantirá que apenas um único thread possa executar o código fechado com o objeto bloqueado. Nesse caso, recomendamos que você coloque esse código em um try bloco e coloque a chamada para o Exit método em um finally bloco. Isso garante que o bloqueio seja liberado mesmo se ocorrer uma exceção. O fragmento de código a seguir ilustra esse padrão.

// Define the lock object.
var obj = new Object();

// Define the critical section.
Monitor.Enter(obj);
try
{
    // Code to execute one thread at a time.
}
// catch blocks go here.
finally
{
    Monitor.Exit(obj);
}
' Define the lock object.
Dim obj As New Object()

' Define the critical section.
Monitor.Enter(obj)
Try
    ' Code to execute one thread at a time.

    ' catch blocks go here.
Finally
    Monitor.Exit(obj)
End Try

Essa instalação normalmente é usada para sincronizar o acesso a um método estático ou de instância de uma classe.

Se uma seção crítica abranger um método inteiro, a instalação de bloqueio poderá ser obtida colocando o System.Runtime.CompilerServices.MethodImplAttribute no método e especificando o valor de Synchronized no construtor de System.Runtime.CompilerServices.MethodImplAttribute. Quando você usa esse atributo, as chamadas dos métodos Enter e Exit não são necessárias. O seguinte fragmento de código ilustra esse padrão:

[MethodImplAttribute(MethodImplOptions.Synchronized)]
void MethodToLock()
{
    // Method implementation.
}
<MethodImplAttribute(MethodImplOptions.Synchronized)>
Sub MethodToLock()
    ' Method implementation.
End Sub

Observe que o atributo faz com que o thread atual mantenha o bloqueio até que o método retorne; se o bloqueio puder ser liberado mais cedo, use a Monitor classe, a instrução de bloqueio C# ou a instrução SyncLock do Visual Basic dentro do método em vez do atributo.

Embora seja possível que as instruções Enter e Exit que bloqueiam e liberam um determinado objeto cruzem limites de membro, de classe ou ambos, essa prática não é recomendada.

Pulse, PulseAll e Wait

Depois que uma thread possui o bloqueio e entra na seção crítica que o bloqueio protege, ela pode chamar os métodos Monitor.Wait, Monitor.Pulse e Monitor.PulseAll.

Quando a thread que segura o bloqueio chama Wait, o bloqueio é liberado e a thread é adicionada à fila de espera do objeto sincronizado. O primeiro thread na fila pronta, se houver, adquire o bloqueio e insere a seção crítica. O thread que chamou Wait é movido da fila de espera para a fila pronta quando o método Monitor.Pulse ou o método Monitor.PulseAll é chamado pelo thread que mantém o bloqueio (para ser movido, o thread deve estar no início da fila de espera). O método Wait retorna quando o thread de chamada readquire o bloqueio.

Quando o thread que detém o bloqueio chama Pulse, o thread na cabeça da fila de espera é movido para a fila pronta. A chamada para o método PulseAll move todos os threads da fila de espera para a fila pronta.

Monitores e identificadores de espera

É importante observar a distinção entre o uso da Monitor classe e WaitHandle dos objetos.

  • A Monitor classe é puramente gerenciada, totalmente portátil e pode ser mais eficiente em termos de requisitos de recursos do sistema operacional.
  • WaitHandle os objetos representam objetos que podem ser esperados do sistema operacional, são úteis para sincronizar entre código gerenciado e não gerenciado e revelam alguns recursos avançados do sistema operacional, como a capacidade de esperar por vários objetos ao mesmo tempo.

Exemplos

O exemplo a seguir usa a classe Monitor para sincronizar o acesso a uma única instância de um gerador de número aleatório representado pela classe Random. O exemplo cria dez tarefas, cada uma das quais é executada de forma assíncrona em um thread do pool de threads. Cada tarefa gera 10.000 números aleatórios, calcula sua média e atualiza duas variáveis de nível de procedimento que mantêm um total em execução do número de números aleatórios gerados e sua soma. Depois que todas as tarefas tiverem sido executadas, esses dois valores serão usados para calcular a média geral.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example2
{
    public static void Main()
    {
        List<Task> tasks = new List<Task>();
        Random rnd = new Random();
        long total = 0;
        int n = 0;

        for (int taskCtr = 0; taskCtr < 10; taskCtr++)
            tasks.Add(Task.Run(() =>
            {
                int[] values = new int[10000];
                int taskTotal = 0;
                int taskN = 0;
                int ctr = 0;
                Monitor.Enter(rnd);
                // Generate 10,000 random integers
                for (ctr = 0; ctr < 10000; ctr++)
                    values[ctr] = rnd.Next(0, 1001);
                Monitor.Exit(rnd);
                taskN = ctr;
                foreach (var value in values)
                    taskTotal += value;

                Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
                                  Task.CurrentId, (taskTotal * 1.0) / taskN,
                                  taskN);
                Interlocked.Add(ref n, taskN);
                Interlocked.Add(ref total, taskTotal);
            }));
        try
        {
            Task.WaitAll(tasks.ToArray());
            Console.WriteLine($"\nMean for all tasks: {(total * 1.0) / n:N2} (N={n:N0})");
        }
        catch (AggregateException e)
        {
            foreach (var ie in e.InnerExceptions)
                Console.WriteLine($"{ie.GetType().Name}: {ie.Message}");
        }
    }
}
// The example displays output like the following:
//       Mean for task  1: 499.04 (N=10,000)
//       Mean for task  2: 500.42 (N=10,000)
//       Mean for task  3: 499.65 (N=10,000)
//       Mean for task  8: 502.59 (N=10,000)
//       Mean for task  5: 502.75 (N=10,000)
//       Mean for task  4: 494.88 (N=10,000)
//       Mean for task  7: 499.22 (N=10,000)
//       Mean for task 10: 496.45 (N=10,000)
//       Mean for task  6: 499.75 (N=10,000)
//       Mean for task  9: 502.79 (N=10,000)
//
//       Mean for all tasks: 499.75 (N=100,000)
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Module Example4
    Public Sub Main()
        Dim tasks As New List(Of Task)()
        Dim rnd As New Random()
        Dim total As Long = 0
        Dim n As Integer = 0

        For taskCtr As Integer = 0 To 9
            tasks.Add(Task.Run(Sub()
                                   Dim values(9999) As Integer
                                   Dim taskTotal As Integer = 0
                                   Dim taskN As Integer = 0
                                   Dim ctr As Integer = 0
                                   Monitor.Enter(rnd)
                                   ' Generate 10,000 random integers.
                                   For ctr = 0 To 9999
                                       values(ctr) = rnd.Next(0, 1001)
                                   Next
                                   Monitor.Exit(rnd)
                                   taskN = ctr
                                   For Each value In values
                                       taskTotal += value
                                   Next

                                   Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
                                                  Task.CurrentId, taskTotal / taskN,
                                                  taskN)
                                   Interlocked.Add(n, taskN)
                                   Interlocked.Add(total, taskTotal)
                               End Sub))
        Next

        Try
            Task.WaitAll(tasks.ToArray())
            Console.WriteLine()
            Console.WriteLine("Mean for all tasks: {0:N2} (N={1:N0})",
                           (total * 1.0) / n, n)
        Catch e As AggregateException
            For Each ie In e.InnerExceptions
                Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message)
            Next
        End Try
    End Sub
End Module
' The example displays output like the following:
'       Mean for task  1: 499.04 (N=10,000)
'       Mean for task  2: 500.42 (N=10,000)
'       Mean for task  3: 499.65 (N=10,000)
'       Mean for task  8: 502.59 (N=10,000)
'       Mean for task  5: 502.75 (N=10,000)
'       Mean for task  4: 494.88 (N=10,000)
'       Mean for task  7: 499.22 (N=10,000)
'       Mean for task 10: 496.45 (N=10,000)
'       Mean for task  6: 499.75 (N=10,000)
'       Mean for task  9: 502.79 (N=10,000)
'
'       Mean for all tasks: 499.75 (N=100,000)

Como elas podem ser acessadas de qualquer tarefa em execução em um thread do pool de threads, o acesso às variáveis total e n também deve ser sincronizado. O Interlocked.Add método é usado para essa finalidade.

O exemplo a seguir demonstra o uso combinado da classe Monitor (implementado com o constructo lock ou SyncLock), a classe Interlocked e a classe AutoResetEvent. Ele define duas classes, internal (em C#) ou Friend (no Visual Basic), SyncResource e UnSyncResource, que fornecem, respectivamente, acesso sincronizado e não sincronizado a um recurso. Para garantir que o exemplo ilustra a diferença entre o acesso sincronizado e não sincronizado (o que pode ser o caso se cada chamada de método for concluída rapidamente), o método inclui um atraso aleatório: para threads cuja Thread.ManagedThreadId propriedade está uniforme, o método chama Thread.Sleep para introduzir um atraso de 2.000 milissegundos. Observe que, como a SyncResource classe não é pública, nenhum código do cliente usa um bloqueio no recurso sincronizado; a própria classe interna usa o bloqueio. Isso impede que o código mal-intencionado bloqueie um objeto público.

using System;
using System.Threading;

internal class SyncResource
{
    // Use a monitor to enforce synchronization.
    public void Access()
    {
        lock(this) {
            Console.WriteLine($"Starting synchronized resource access on thread #{Thread.CurrentThread.ManagedThreadId}");
            if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
                Thread.Sleep(2000);

            Thread.Sleep(200);
            Console.WriteLine($"Stopping synchronized resource access on thread #{Thread.CurrentThread.ManagedThreadId}");
        }
    }
}

internal class UnSyncResource
{
    // Do not enforce synchronization.
    public void Access()
    {
        Console.WriteLine($"Starting unsynchronized resource access on Thread #{Thread.CurrentThread.ManagedThreadId}");
        if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
            Thread.Sleep(2000);

        Thread.Sleep(200);
        Console.WriteLine($"Stopping unsynchronized resource access on thread #{Thread.CurrentThread.ManagedThreadId}");
    }
}

public class App
{
    private static int numOps;
    private static AutoResetEvent opsAreDone = new AutoResetEvent(false);
    private static SyncResource SyncRes = new SyncResource();
    private static UnSyncResource UnSyncRes = new UnSyncResource();

   public static void Main()
   {
        // Set the number of synchronized calls.
        numOps = 5;
        for (int ctr = 0; ctr <= 4; ctr++)
            ThreadPool.QueueUserWorkItem(new WaitCallback(SyncUpdateResource));

        // Wait until this WaitHandle is signaled.
        opsAreDone.WaitOne();
        Console.WriteLine("\t\nAll synchronized operations have completed.\n");

        // Reset the count for unsynchronized calls.
        numOps = 5;
        for (int ctr = 0; ctr <= 4; ctr++)
            ThreadPool.QueueUserWorkItem(new WaitCallback(UnSyncUpdateResource));

        // Wait until this WaitHandle is signaled.
        opsAreDone.WaitOne();
        Console.WriteLine("\t\nAll unsynchronized thread operations have completed.\n");
   }

    static void SyncUpdateResource(Object state)
    {
        // Call the internal synchronized method.
        SyncRes.Access();

        // Ensure that only one thread can decrement the counter at a time.
        if (Interlocked.Decrement(ref numOps) == 0)
            // Announce to Main that in fact all thread calls are done.
            opsAreDone.Set();
    }

    static void UnSyncUpdateResource(Object state)
    {
        // Call the unsynchronized method.
        UnSyncRes.Access();

        // Ensure that only one thread can decrement the counter at a time.
        if (Interlocked.Decrement(ref numOps) == 0)
            // Announce to Main that in fact all thread calls are done.
            opsAreDone.Set();
    }
}
// The example displays output like the following:
//    Starting synchronized resource access on thread #6
//    Stopping synchronized resource access on thread #6
//    Starting synchronized resource access on thread #7
//    Stopping synchronized resource access on thread #7
//    Starting synchronized resource access on thread #3
//    Stopping synchronized resource access on thread #3
//    Starting synchronized resource access on thread #4
//    Stopping synchronized resource access on thread #4
//    Starting synchronized resource access on thread #5
//    Stopping synchronized resource access on thread #5
//
//    All synchronized operations have completed.
//
//    Starting unsynchronized resource access on Thread #7
//    Starting unsynchronized resource access on Thread #9
//    Starting unsynchronized resource access on Thread #10
//    Starting unsynchronized resource access on Thread #6
//    Starting unsynchronized resource access on Thread #3
//    Stopping unsynchronized resource access on thread #7
//    Stopping unsynchronized resource access on thread #9
//    Stopping unsynchronized resource access on thread #3
//    Stopping unsynchronized resource access on thread #10
//    Stopping unsynchronized resource access on thread #6
//
//    All unsynchronized thread operations have completed.
Imports System.Threading

Friend Class SyncResource
    ' Use a monitor to enforce synchronization.
    Public Sub Access()
        SyncLock Me
            Console.WriteLine("Starting synchronized resource access on thread #{0}",
                              Thread.CurrentThread.ManagedThreadId)
            If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
                Thread.Sleep(2000)
            End If
            Thread.Sleep(200)
            Console.WriteLine("Stopping synchronized resource access on thread #{0}",
                              Thread.CurrentThread.ManagedThreadId)
        End SyncLock
    End Sub
End Class

Friend Class UnSyncResource
    ' Do not enforce synchronization.
    Public Sub Access()
        Console.WriteLine("Starting unsynchronized resource access on Thread #{0}",
                          Thread.CurrentThread.ManagedThreadId)
        If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
            Thread.Sleep(2000)
        End If
        Thread.Sleep(200)
        Console.WriteLine("Stopping unsynchronized resource access on thread #{0}",
                          Thread.CurrentThread.ManagedThreadId)
    End Sub
End Class

Public Module App
    Private numOps As Integer
    Private opsAreDone As New AutoResetEvent(False)
    Private SyncRes As New SyncResource()
    Private UnSyncRes As New UnSyncResource()

    Public Sub Main()
        ' Set the number of synchronized calls.
        numOps = 5
        For ctr As Integer = 0 To 4
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf SyncUpdateResource))
        Next
        ' Wait until this WaitHandle is signaled.
        opsAreDone.WaitOne()
        Console.WriteLine(vbTab + Environment.NewLine + "All synchronized operations have completed.")
        Console.WriteLine()

        numOps = 5
        ' Reset the count for unsynchronized calls.
        For ctr As Integer = 0 To 4
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf UnSyncUpdateResource))
        Next

        ' Wait until this WaitHandle is signaled.
        opsAreDone.WaitOne()
        Console.WriteLine(vbTab + Environment.NewLine + "All unsynchronized thread operations have completed.")
    End Sub

    Sub SyncUpdateResource()
        ' Call the internal synchronized method.
        SyncRes.Access()

        ' Ensure that only one thread can decrement the counter at a time.
        If Interlocked.Decrement(numOps) = 0 Then
            ' Announce to Main that in fact all thread calls are done.
            opsAreDone.Set()
        End If
    End Sub

    Sub UnSyncUpdateResource()
        ' Call the unsynchronized method.
        UnSyncRes.Access()

        ' Ensure that only one thread can decrement the counter at a time.
        If Interlocked.Decrement(numOps) = 0 Then
            ' Announce to Main that in fact all thread calls are done.
            opsAreDone.Set()
        End If
    End Sub
End Module
' The example displays output like the following:
'    Starting synchronized resource access on thread #6
'    Stopping synchronized resource access on thread #6
'    Starting synchronized resource access on thread #7
'    Stopping synchronized resource access on thread #7
'    Starting synchronized resource access on thread #3
'    Stopping synchronized resource access on thread #3
'    Starting synchronized resource access on thread #4
'    Stopping synchronized resource access on thread #4
'    Starting synchronized resource access on thread #5
'    Stopping synchronized resource access on thread #5
'
'    All synchronized operations have completed.
'
'    Starting unsynchronized resource access on Thread #7
'    Starting unsynchronized resource access on Thread #9
'    Starting unsynchronized resource access on Thread #10
'    Starting unsynchronized resource access on Thread #6
'    Starting unsynchronized resource access on Thread #3
'    Stopping unsynchronized resource access on thread #7
'    Stopping unsynchronized resource access on thread #9
'    Stopping unsynchronized resource access on thread #3
'    Stopping unsynchronized resource access on thread #10
'    Stopping unsynchronized resource access on thread #6
'
'    All unsynchronized thread operations have completed.

O exemplo define uma variável, numOpsque define o número de threads que tentarão acessar o recurso. O thread do aplicativo chama o ThreadPool.QueueUserWorkItem(WaitCallback) método para acesso sincronizado e não sincronizado cinco vezes cada. O ThreadPool.QueueUserWorkItem(WaitCallback) método tem um único parâmetro, um delegado que não aceita parâmetros e não retorna nenhum valor. Para acesso sincronizado, ele invoca o SyncUpdateResource método; para acesso não sincronizado, ele invoca o UnSyncUpdateResource método. Após cada conjunto de chamadas de método, o thread do aplicativo invoca o método AutoResetEvent.WaitOne para que ele bloqueie até que a instância AutoResetEvent seja sinalizada.

Cada chamada do método SyncUpdateResource chama o método interno SyncResource.Access e, em seguida, chama o método Interlocked.Decrement para decrementar o contador numOps. O Interlocked.Decrement método é usado para decrementar o contador, pois caso contrário, você não pode ter certeza de que uma segunda thread acessará o valor antes que o valor decrementado de uma primeira thread tenha sido armazenado na variável. Quando o último thread de trabalhador sincronizado decrementa o contador para zero, indicando que todos os threads sincronizados terminaram de acessar o recurso, o método SyncUpdateResource chama o método EventWaitHandle.Set, que sinaliza o thread principal para continuar a execução.

Cada chamada do método UnSyncUpdateResource chama o método interno UnSyncResource.Access e, em seguida, chama o método Interlocked.Decrement para decrementar o contador numOps. Mais uma vez, o método Interlocked.Decrement é utilizado para decrementar o contador, garantindo que uma segunda thread não acesse o valor antes que o valor decrementado da primeira thread tenha sido atribuído à variável. Quando o último thread de trabalho não sincronizado diminui o contador para zero, indicando que não é necessário mais threads não sincronizados para acessar o recurso, o UnSyncUpdateResource método chama o EventWaitHandle.Set método, o que sinaliza o thread principal para continuar a execução.

Como mostra a saída do exemplo, o acesso sincronizado garante que o thread de chamada saia do recurso protegido antes que outro thread possa acessá-lo; cada thread aguarda em seu antecessor. Por outro lado, sem o bloqueio, o método UnSyncResource.Access é chamado na ordem em que os threads o alcançam.