Monitor Classe

Definição

Fornece um mecanismo que sincroniza o acesso a objetos.

public ref class Monitor abstract sealed
public ref class Monitor sealed
public static class Monitor
public sealed class Monitor
[System.Runtime.InteropServices.ComVisible(true)]
public static class Monitor
type Monitor = class
[<System.Runtime.InteropServices.ComVisible(true)>]
type Monitor = class
Public Class Monitor
Public NotInheritable Class Monitor
Herança
Monitor
Atributos

Exemplos

O exemplo a seguir usa a classe Monitor para sincronizar o acesso a uma única instância de um gerador de números aleatórios representado pela classe Random. O exemplo cria dez tarefas, cada uma das quais é executada de forma assíncrona num 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 contínuo do número de números aleatórios gerados e sua soma. Depois que todas as tarefas forem executadas, esses dois valores são usados para calcular a média geral.

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

public class Example
{
   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 as variáveis total e n podem ser acessadas a partir de qualquer tarefa em execução numa thread do pool de threads, o acesso a essas variáveis deve ser sincronizado. O Interlocked.Add método é utilizado para este fim.

O exemplo a seguir demonstra o uso combinado da classe Monitor (implementada com a construção lock ou SyncLock), da classe Interlocked e da classe AutoResetEvent. Ele define duas internal (em C#) ou Friend (em Visual Basic) classes SyncResource e UnSyncResource, que respectivamente fornecem acesso sincronizado e nã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 é par, o método chama Thread.Sleep para introduzir um atraso de 2.000 milissegundos. Observe que, como a classe SyncResource não é pública, nenhum código do cliente adquire um bloqueio no recurso sincronizado; a própria classe interna adquire o bloqueio. Isso impede 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, numOps, que define o número de threads que tentarão acessar o recurso. A thread do aplicativo chama o método ThreadPool.QueueUserWorkItem(WaitCallback) 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 chama o método AutoResetEvent.WaitOne para que ele bloqueie até que a AutoResetEvent instância seja sinalizada.

Cada chamada para o método SyncUpdateResource chama o método interno SyncResource.Access e, em seguida, o método Interlocked.Decrement para diminuir o contador numOps. O Interlocked.Decrement método é usado para decrementar o contador, porque caso contrário não se pode ter a certeza de que uma segunda thread irá aceder ao valor antes de o valor decrementado da primeira thread ter 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 SyncUpdateResource método chama o EventWaitHandle.Set método, que sinaliza o thread principal para continuar a execução.

Cada chamada para o método UnSyncUpdateResource chama o método interno UnSyncResource.Access e, em seguida, o método Interlocked.Decrement para diminuir o contador numOps. Mais uma vez, o Interlocked.Decrement método é usado para decrementar o contador para garantir que uma segunda thread não acede ao valor antes de o valor decrementado da primeira thread ter 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, que sinaliza o thread principal para continuar a execução.

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

Observações

A classe Monitor permite sincronizar o acesso a uma região de código obtendo e libertando um bloqueio em um objeto específico ao chamar 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, comumente 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 executado pelo proprietário do bloqueio, a 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.

Descrição geral

Monitor tem as seguintes características:

  • Está associado a um objeto a pedido.
  • É ilimitado, 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. A cada método é passado o objeto sincronizado que controla o acesso à seção crítica.

Note

Use a Monitor classe para bloquear objetos diferentes de cadeias de caracteres (ou seja, tipos de referência diferentes de String), 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 Description
Enter, TryEnter Adquire um bloqueio para um objeto. Esta ação marca também o início de uma secçã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 de espera que o estado do objeto bloqueado foi alterado e que o proprietário do bloqueio está pronto para o liberar. O thread de espera é colocado na fila pronta do objeto para que ele possa, eventualmente, receber o bloqueio para o objeto. Uma vez que o thread tem o bloqueio, ele pode 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.

Existem dois conjuntos de sobrecargas para os Enter métodos e TryEnter . Um conjunto de sobrecargas tem um parâmetro ref (em C#) ou ByRef (em Visual Basic) Boolean que é configurado atomicamente como true se o bloqueio for adquirido, mesmo que uma exceção seja lançada ao adquirir o bloqueio. Use essas sobrecargas se for crítico 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 static (Shared no Visual Basic) métodos que operam em um objeto que controla o acesso à secção crítica. As seguintes informações são mantidas para cada objeto sincronizado:

  • Uma referência ao thread que atualmente manté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 se possa passar um tipo de valor para Enter e Exit, ele é boxeado separadamente para cada chamada. Como cada chamada cria um objeto separado, Enter nunca bloqueia, e o código que 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 SynchronizationLockException exceção com a mensagem "O método de sincronização de objetos foi chamado a partir 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 da modificação simultânea 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 Example
{
   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 variável nTasks é encaixotada antes da chamada ao método Monitor.Enter em cada tarefa. Em outras palavras, cada chamada de método é passada uma variável separada que é independente das outras. nTasks é novamente encaixotado 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 é 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 se possa boxear uma variável de tipo de valor antes de chamar Enter e Exit, como mostrado no exemplo a seguir, e passar o mesmo objeto boxeado para ambos os métodos, não há vantagem em fazê-lo. As alterações na variável não empacotada não são refletidas na cópia empacotada e não há como alterar o valor da cópia empacotada.

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 de 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 secção crítica

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

Note

A funcionalidade fornecida pelos métodos Enter e Exit é a mesma que a declaração lock em C# e a declaração SyncLock no Visual Basic, exceto que as construções da linguagem encapsulam a sobrecarga do método Monitor.Enter(Object, Boolean) e o método Monitor.Exit num try...bloco 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 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 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

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 abranger um método inteiro, o mecanismo de bloqueio pode ser alcançado colocando o System.Runtime.CompilerServices.MethodImplAttribute no método e especificando o valor de Synchronized no construtor de System.Runtime.CompilerServices.MethodImplAttribute. Quando o utilizador utiliza este atributo, as chamadas de método Enter 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 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 os limites de membro, 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, pode chamar os métodos Monitor.Wait, Monitor.Pulse e 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 Wait é movido da fila de espera para a fila pronta quando o Monitor.Pulse ou o Monitor.PulseAll método é chamado pelo thread que contém o bloqueio (para ser movido, o 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 topo 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 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 de 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 muitos objetos ao mesmo tempo.

Propriedades

Name Descrição
LockContentionCount

Mostra o número de vezes que houve contenção ao tentar tirar o bloqueio do monitor.

Métodos

Name Descrição
Enter(Object, Boolean)

Adquire um bloqueio exclusivo no objeto especificado e define atomicamente um valor que indica se o bloqueio foi tomado.

Enter(Object)

Adquire um bloqueio exclusivo sobre o objeto especificado.

Exit(Object)

Liberta um bloqueio exclusivo sobre o objeto especificado.

IsEntered(Object)

Determina se a thread atual mantém o bloqueio no objeto especificado.

Pulse(Object)

Notifica um fio na fila de espera sobre uma alteração no estado do objeto bloqueado.

PulseAll(Object)

Notifica todos os fios em espera de uma alteração no estado do objeto.

TryEnter(Object, Boolean)

Tenta adquirir um bloqueio exclusivo sobre o objeto especificado, e define atomicamente um valor que indica se o bloqueio foi tomado.

TryEnter(Object, Int32, Boolean)

Tenta, pelo número especificado de milissegundos, adquirir um bloqueio exclusivo no objeto especificado e define atomicamente um valor que indica se o bloqueio foi tomado.

TryEnter(Object, Int32)

Tenta, pelo número especificado de milissegundos, obter um bloqueio exclusivo sobre o objeto especificado.

TryEnter(Object, TimeSpan, Boolean)

Tenta, pelo tempo especificado, adquirir um bloqueio exclusivo no objeto especificado e define atomicamente um valor que indica se o bloqueio foi tomado.

TryEnter(Object, TimeSpan)

Tenta, durante o tempo especificado, obter um bloqueio exclusivo sobre o objeto especificado.

TryEnter(Object)

Tenta obter um bloqueio exclusivo sobre o objeto especificado.

Wait(Object, Int32, Boolean)

Liberta o bloqueio de um objeto e bloqueia o thread atual até que este recupere o bloqueio. Se o intervalo de tempo especificado expirar, a thread entra na fila pronta. Este método também especifica se o domínio de sincronização para o contexto (se estiver num contexto sincronizado) é encerrado antes da espera e readquirido depois.

Wait(Object, Int32)

Liberta o bloqueio de um objeto e bloqueia o thread atual até que este recupere o bloqueio. Se o intervalo de tempo especificado expirar, a thread entra na fila pronta.

Wait(Object, TimeSpan, Boolean)

Liberta o bloqueio de um objeto e bloqueia o thread atual até que este recupere o bloqueio. Se o intervalo de tempo especificado expirar, a thread entra na fila pronta. Opcionalmente, sai do domínio de sincronização para o contexto sincronizado antes da espera e recupera o domínio depois.

Wait(Object, TimeSpan)

Liberta o bloqueio de um objeto e bloqueia o thread atual até que este recupere o bloqueio. Se o intervalo de tempo especificado expirar, a thread entra na fila pronta.

Wait(Object)

Liberta o bloqueio de um objeto e bloqueia o thread atual até que este recupere o bloqueio.

Aplica-se a

Segurança de Thread

Este tipo é seguro para fios.

Ver também