System.Threading.Monitor classe

Este artigo fornece observações complementares à documentação de referência para essa API.

A Monitor classe permite que você sincronize o acesso a uma região de código pegando e liberando um bloqueio em um objeto específico chamando os Monitor.Entermétodos , Monitor.TryEntere Monitor.Exit . Os bloqueios de objeto fornecem a capacidade de restringir o acesso a um bloco de código, comumente chamado de seção crítica. Enquanto um thread possui o bloqueio para um objeto, nenhum outro thread pode adquirir esse bloqueio. Você também pode usar a classe para garantir que nenhum outro thread tenha permissão para acessar uma seção do código do aplicativo que está sendo executado pelo proprietário do bloqueio, a Monitor menos que o outro thread esteja executando o código usando um objeto bloqueado diferente. Como 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 as seguintes características:

  • Ele está associado a um objeto sob demanda.
  • Ele é desvinculado, 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 passa o objeto sincronizado que controla o acesso à seção crítica.

Observação

Use a Monitor classe para bloquear objetos diferentes de cadeias de caracteres (ou seja, tipos de referência diferentes de ), não tipos de Stringvalor. Para obter detalhes, consulte as Enter sobrecargas do método 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. Esta 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. Os sinais de pulso são usados para notificar threads em espera sobre alterações no estado de um objeto.
Pulse (sinal), PulseAll Envia um sinal para um ou mais threads em espera. O sinal notifica um thread em espera de que o estado do objeto bloqueado foi alterado e o proprietário do bloqueio está pronto para liberar o bloqueio. O thread em espera é colocado na fila pronta do objeto para que ele possa eventualmente receber o bloqueio para o objeto. Depois que o thread tiver o bloqueio, ele poderá verificar o novo estado do objeto para ver se o estado necessário 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 o Enter e TryEnter métodos. Um conjunto de sobrecargas tem um ref parâmetro (em C#) ou ByRef (no Visual Basic) Boolean que é definido atomicamente como true se o bloqueio for adquirido, mesmo se uma exceção for lançada ao adquirir o bloqueio. Use essas sobrecargas se for fundamental 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 static em (Shared no Visual Basic) métodos 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 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 passar um tipo de valor para e Exit, ele é encaixotado separadamente para Enter cada chamada. Como cada chamada cria um objeto separado, nunca bloqueia, Enter e o código que supostamente está protegendo não é realmente sincronizado. Além disso, o objeto passado para é diferente do objeto passado para ExitEnter, portanto Monitor , lança SynchronizationLockException exceção com a mensagem "O método de sincronização de objeto foi chamado de um bloco de código não sincronizado".

O exemplo a seguir ilustra esse problema. Ele lança dez tarefas, cada uma das quais apenas dorme por 250 milissegundos. Em seguida, cada tarefa atualiza uma variável de contador, nTasks, que 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 de modificações simultâneas por várias tarefas. No entanto, como mostra a saída do exemplo, cada uma das tarefas lança 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("{0} tasks started and executed.", nTasks);
        }
        catch (AggregateException e)
        {
            String msg = String.Empty;
            foreach (var ie in e.InnerExceptions)
            {
                Console.WriteLine("{0}", 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 lança uma SynchronizationLockException exceção porque a nTasks variável é encaixotada antes da chamada para o Monitor.Enter método em cada tarefa. Em outras palavras, cada chamada de método é passada uma variável separada que é independente das outras. nTasks é encaixotado novamente na chamada para o Monitor.Exit método. Mais uma vez, isso cria dez novas variáveis em caixa, que são independentes entre si, nTaskse as dez variáveis em caixa criadas na chamada para o Monitor.Enter método. A exceção é lançada, 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 encaixotar uma variável de tipo de valor antes de chamar Enter e , como mostrado no exemplo a seguir, e Exitpassar o mesmo objeto in a box para ambos os métodos, não há vantagem em fazer isso. As alterações na variável unboxed não são refletidas na cópia in a box e não há como alterar o valor da cópia in a box.

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("{0} tasks started and executed.", nTasks);
      }
      catch (AggregateException e) {
         String msg = String.Empty;
         foreach (var ie in e.InnerExceptions) {
            Console.WriteLine("{0}", 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 apenas objetos particulares ou internos. O bloqueio em objetos externos pode resultar em deadlocks, porque o código não relacionado pode escolher os mesmos objetos para bloquear para fins diferentes.

Observe que você pode sincronizar em um objeto em vários domínios de aplicativo se o objeto usado para o bloqueio derivar de MarshalByRefObject.

A seção crítica

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

Observação

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

Se a seção crítica for um conjunto de instruções contíguas Enter , o bloqueio adquirido pelo método garante que apenas um único thread possa executar o código incluído com o objeto bloqueado. Nesse caso, recomendamos que você coloque esse código em um bloco e faça a chamada para o Exit método em um tryfinally 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

Esse recurso normalmente é usado para sincronizar o acesso a um método estático ou de instância de uma classe.

Se uma seção crítica abrange um método inteiro, o recurso de bloqueio pode ser obtido colocando o no método e especificando o System.Runtime.CompilerServices.MethodImplAttributeSynchronized valor no construtor de System.Runtime.CompilerServices.MethodImplAttribute. Quando você usa esse atributo, as Enter chamadas de método e Exit não são necessárias. O fragmento de código a seguir 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 classe, a instrução C# lock ou a Monitor instrução SyncLock do Visual Basic dentro do método em vez do atributo.

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

Pulse, PulseAll e Aguarde

Depois que um thread possui o bloqueio e entra na seção crítica que o bloqueio protege, ele pode chamar os Monitor.Waitmétodos , Monitor.Pulsee Monitor.PulseAll .

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

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

Monitores e alças de espera

É importante notar a distinção entre o uso da Monitor classe e WaitHandle os 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 em espera do sistema operacional, são úteis para sincronizar entre código gerenciado e não gerenciado e expõem alguns recursos avançados do sistema operacional, como a capacidade de esperar em vários objetos ao mesmo tempo.

Exemplos

O exemplo a seguir usa a classe para sincronizar o acesso a Monitor uma única instância de um gerador de números aleatórios representado pela Random classe. O exemplo cria dez tarefas, cada uma das quais é executada de forma assíncrona em um thread de 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 forem 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: {0:N2} (N={1:N0})",
                              (total * 1.0) / n, n);
        }
        catch (AggregateException e)
        {
            foreach (var ie in e.InnerExceptions)
                Console.WriteLine("{0}: {1}", 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 eles podem ser acessados de qualquer tarefa em execução em um thread de pool de threads, o acesso às variáveis total e n também deve ser sincronizado. O Interlocked.Add método é utilizado para este fim.

O exemplo a seguir demonstra o uso combinado da classe (implementada com a lock construção ou SyncLock language), da classe e da AutoResetEventMonitorInterlocked classe. Ele define duas internal (em C#) ou Friend (em Visual Basic) classes SyncResource e , que respectivamente fornecem acesso sincronizado e UnSyncResourcenão sincronizado a um recurso. Para garantir que o exemplo ilustre 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 é uniforme, o método chama Thread.Sleep para introduzir um atraso de 2.000 milissegundos. Observe que, como a classe não é pública, nenhum código do cliente usa um bloqueio no recurso sincronizado, a SyncResource própria classe interna usa o bloqueio. Isso evita que códigos mal-intencionados bloqueiem 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 #{0}",
                              Thread.CurrentThread.ManagedThreadId);
            if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
                Thread.Sleep(2000);

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

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

        Thread.Sleep(200);
        Console.WriteLine("Stopping unsynchronized resource access on thread #{0}",
                          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 método para acesso sincronizado e não sincronizado ThreadPool.QueueUserWorkItem(WaitCallback) 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 método, para acesso não sincronizado, ele invoca o SyncUpdateResourceUnSyncUpdateResource método. Após cada conjunto de chamadas de método, o thread do aplicativo chama o método AutoResetEvent.WaitOne para que ele bloqueie até que a AutoResetEvent instância seja sinalizada.

Cada chamada para o método chama o método interno SyncResource.Access e, em seguida, chama o método para decrement o numOpsSyncUpdateResourceInterlocked.Decrement contador. O Interlocked.Decrement método é usado para decrementar o contador, porque, caso contrário, você não pode ter certeza de que um segundo thread acessará o valor antes que o valor decremented de um primeiro thread tenha sido armazenado na variável. Quando o último thread de trabalho sincronizado diminui o contador para zero, indicando que todos os threads sincronizados concluíram o acesso ao recurso, o método chama o método, que sinaliza o SyncUpdateResourceEventWaitHandle.Set thread principal para continuar a execução.

Cada chamada para o método chama o método interno UnSyncResource.Access e, em seguida, chama o método para decrement o numOpsUnSyncUpdateResourceInterlocked.Decrement contador. Mais uma vez, o método é usado para diminuir o contador para garantir que um segundo thread não acesse o Interlocked.Decrement valor antes que o valor decremented de um primeiro thread tenha sido atribuído à variável. Quando o último thread de trabalho não sincronizado diminui o contador para zero, indicando que nenhum thread não sincronizado precisa acessar o recurso, o método chama o método, que sinaliza o UnSyncUpdateResourceEventWaitHandle.Set 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 UnSyncResource.Access método é chamado na ordem em que os fios o alcançam.