Monitor Класс

Определение

Предоставляет механизм для синхронизации доступа к объектам.

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
Наследование
Monitor
Атрибуты

Примеры

В следующем примере класс используется Monitor для синхронизации доступа к одному экземпляру генератора случайных чисел, представленного классом Random . В примере создаются десять задач, каждая из которых выполняется асинхронно в потоке пула потоков. Каждая задача создает 10 000 случайных чисел, вычисляет их среднее значение и обновляет две переменные уровня процедуры, которые поддерживают выполнение общего числа случайных чисел и их суммы. После выполнения всех задач эти два значения затем используются для вычисления общего среднего значения.

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 Example
   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)

Так как к ней можно получить доступ из любой задачи, выполняемой в потоке пула потоков, доступ к переменным total и n также должен быть синхронизирован. Этот Interlocked.Add метод используется для этой цели.

В следующем примере демонстрируется объединенное использование Monitor класса (реализованного с lock помощью конструкции или SyncLock языка), Interlocked класса и AutoResetEvent класса. Он определяет два класса internal (в C#) или Friend (в Visual Basic), SyncResource и UnSyncResource, которые соответственно предоставляют синхронизированный и несинхронизированный доступ к ресурсу. Чтобы обеспечить демонстрацию в примере различия между синхронизированным и несинхронизированным доступом (что может случиться, если каждый вызов метода завершается быстро), метод включает случайную задержку: для потоков, свойство Thread.ManagedThreadId которых имеет четное значение, метод вызывает Thread.Sleep для введения задержки в 2 000 миллисекунд. Обратите внимание, что поскольку класс SyncResource не является общим, ни один клиентский код не выполняет блокировку в синхронизированном ресурсе; внутренний класс сам выполняет блокировку. Это предотвращает блокировка общедоступного объекта вредоносным кодом.

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.

В примере определяется переменная numOps, задающая число потоков, которые будут пытаться получить доступ к ресурсу. Поток приложения вызывает метод ThreadPool.QueueUserWorkItem(WaitCallback) для синхронизированного и несинхронизированного доступа по пять раз. Метод ThreadPool.QueueUserWorkItem(WaitCallback) имеет единственный параметр, делегат, который не принимает никаких параметров и не возвращает значений. Для синхронизированного доступа он вызывает метод SyncUpdateResource; для несинхронизированного доступа он вызывает метод UnSyncUpdateResource. После каждого набора вызовов метода поток приложения вызывает метод AutoResetEvent.WaitOne , чтобы он блокировался до тех пор, пока экземпляр не AutoResetEvent будет сигнализировать.

Каждый вызов метода SyncUpdateResource вызывает внутренний метод SyncResource.Access, а затем вызывает метод Interlocked.Decrement для уменьшения счетчика numOps. Метод Interlocked.Decrement используется для уменьшения счетчика, так как в противном случае невозможно убедиться, что второй поток получит доступ к значению до сохранения в переменной декрементированного значения первого потока. Когда последний синхронизированный рабочий поток уменьшает счетчик до нуля, указывая, что все синхронизированные потоки завершили доступ к ресурсу, метод вызывает EventWaitHandle.Set метод, SyncUpdateResource который сигнализирует основному потоку продолжить выполнение.

Каждый вызов метода UnSyncUpdateResource вызывает внутренний метод UnSyncResource.Access, а затем вызывает метод Interlocked.Decrement для уменьшения счетчика numOps. Еще раз метод используется для уменьшения счетчика, чтобы убедиться, Interlocked.Decrement что второй поток не обращается к значению до назначения переменной декрементированного значения первого потока. Когда последний несинхронизированный рабочий поток уменьшает значение счетчика до нуля, указывая, что для доступа к ресурсу не требуется больше несинхронизированных потоков, UnSyncUpdateResource метод вызывает EventWaitHandle.Set метод, который сигнализирует основному потоку продолжить выполнение.

Как показывает результат этого примера, синхронизированный доступ обеспечивает, что вызывающий поток выходит из защищенного ресурса до того, как другой поток получит доступ к этому ресурсу; каждый поток ожидает своего предшественника. С другой стороны, без блокировки метод UnSyncResource.Access вызывается в том порядке, в котором потоки получают к нему доступ.

Комментарии

Класс Monitor позволяет синхронизировать доступ к области кода, принимая и освобождая блокировку определенного объекта, вызывая Monitor.EnterMonitor.TryEnterметоды и Monitor.Exit т. д. Блокировки объектов позволяют ограничить доступ к блоку кода, который обычно называется критическим разделом. Хотя поток владеет блокировкой для объекта, ни другой поток не может получить такую блокировку. Вы также можете использовать Monitor класс, чтобы гарантировать, что другим потоком не разрешен доступ к разделу кода приложения, выполняемого владельцем блокировки, если другой поток не выполняет код с помощью другого заблокированного объекта.

Содержание этой статьи

Класс Monitor: обзор
Объект блокировки
Критический раздел
Pulse, PulseAll и Wait
Мониторы и дескриптор ожидания

Класс Monitor: обзор

Monitor имеет следующие возможности:

  • Он связан с объектом по запросу.

  • Это означает, что его можно вызывать непосредственно из любого контекста.

  • Невозможно создать экземпляр Monitor класса; методы Monitor класса являются статическими. Каждый метод передает синхронизированный объект, который управляет доступом к критическому разделу.

Примечание

Monitor Используйте класс для блокировки объектов, отличных от строк (то есть ссылочных типов, отличных от Stringтипов значений), а не типов значений. Дополнительные сведения см. в разделах о перегрузках Enter метода и разделе объекта блокировки далее в этой статье.

В следующей таблице описаны действия, которые могут выполняться потоками, которые обращаются к синхронизированным объектам:

Действие Описание
Enter, TryEnter Получает блокировку для объекта. Это действие также знаменует собой начало критического раздела. Никакие другие потоки не могут входить в критический раздел, если только он не выполняет инструкции в критическом разделе с помощью другого заблокированного объекта.
Wait Освобождает блокировку объекта, чтобы разрешить другим потокам блокировать и получать доступ к объекту. Вызывающий поток ожидает, пока другой поток обращается к объекту. Импульсные сигналы используются для уведомления потоков ожидания об изменениях состояния объекта.
Pulse (сигнал), PulseAll Отправляет сигнал одному или нескольким ожидающих потокам. Сигнал уведомляет поток ожидания о том, что состояние заблокированного объекта изменилось, и владелец блокировки готов освободить блокировку. Поток ожидания помещается в очередь готовности объекта, чтобы в конечном итоге он мог получить блокировку для объекта. После блокировки поток может проверить новое состояние объекта, чтобы узнать, достигнуто ли требуемое состояние.
Exit Освобождает блокировку объекта. Это действие также помечает конец критического раздела, защищенного заблокированным объектом.

Начиная с платформа .NET Framework 4 существует два набора перегрузок для Enter и TryEnter методов. Один набор перегрузок имеет ref параметр (в C#) или ByRef (в Visual Basic), Boolean который атомарно устанавливаетсяtrue, если блокировка получена, даже если при получении блокировки возникает исключение. Используйте эти перегрузки, если критически важно освободить блокировку во всех случаях, даже если ресурсы, защищенные блокировкой, могут не находиться в согласованном состоянии.

Объект блокировки

Класс Monitor состоит из методов (в C#) или Shared (в Visual Basic), работающих static с объектом, который управляет доступом к критическому разделу. Для каждого синхронизированного объекта сохраняются следующие сведения:

  • Ссылка на поток, который в настоящее время содержит блокировку.

  • Ссылка на готовую очередь, которая содержит потоки, готовые к получению блокировки.

  • Ссылка на очередь ожидания, содержащая потоки, ожидающие уведомления об изменении состояния заблокированного объекта.

Monitor блокирует объекты (то есть ссылочные типы), а не типы значений. Хотя можно передать тип значения в Enter и Exit, он упаковывается отдельно для каждого вызова. Поскольку при каждом вызове создается отдельный объект, Enter никогда не выполняет блокировку, а код, который он предположительно защищает, на самом деле не синхронизируется. Кроме того, объект, переданный в Exit, отличается от объекта, переданного в Enter, поэтому Monitor вызывает исключение SynchronizationLockException с сообщением «Для не синхронизированного блока кода вызван метод синхронизации объектов».

Приведенный ниже пример иллюстрирует данную проблему. Он запускает десять задач, каждая из которых просто бездействует в течение 250 миллисекунд. Затем каждая задача обновляет переменную счетчика nTasks, который предназначен для подсчета количества фактически запущенных и выполненных задач. Поскольку nTasks является глобальной переменной, которая может обновляться несколькими задачами одновременно, используется монитор, защищающий ее от одновременного изменения несколькими задачами. Тем не менее, как показывают выходные данные в примере, каждая из задач вызывает исключение SynchronizationLockException.

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 Example
   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.

Каждая задача вызывает исключение SynchronizationLockException из-за того, что переменная nTasks упаковывается перед вызовом метода Monitor.Enter в каждой задаче. Другими словами, в каждый вызов метода передается отдельная переменная, которая независима от остальных. nTasks снова упаковывается в вызове метода Monitor.Exit. И снова при этом создается десять новых упакованных переменных, которые не зависят друг от друга, nTasks, и десять упакованных переменных, созданных при вызове метода Monitor.Enter. Затем вызывается исключение, поскольку наш код пытается снять блокировку для вновь созданной переменной, которая ранее не была заблокирована.

Хотя можно упаковать переменную типа значения перед вызовом Enter и Exit, как показано в следующем примере, и передать тот же упакованный объект в оба метода, такой подход не дает никаких преимуществ. Изменения неупакованной переменной не отражаются в упакованной копии, и возможность изменения значения упакованной копии отсутствует.

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 Example
   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.

При выборе объекта, для которого требуется синхронизироваться, следует заблокировать только частные или внутренние объекты. Блокировка внешних объектов может привести к взаимоблокировкам, так как несвязанный код может выбрать одни и те же объекты для блокировки в различных целях.

Обратите внимание, что можно синхронизировать объект в нескольких доменах приложения, если объект, используемый для блокировки, является производным.MarshalByRefObject

Критический раздел

Enter Используйте методы, Exit чтобы пометить начало и конец критического раздела.

Примечание

Функциональные возможности, предоставляемые Enter методами, Exit идентичны тем, которые предоставляются инструкцией lock в C# и инструкцией SyncLock в Visual Basic, за исключением того, что языковые конструкции обтекают перегрузку Monitor.Enter(Object, Boolean) метода и Monitor.Exit метод в ...try``finally block to ensure that the monitor is released (Блокировать, чтобы монитор был освобожден).

Если критический раздел представляет собой набор непрерывных инструкций, блокировка, полученная методом Enter , гарантирует, что только один поток может выполнить заключенный код с заблокированным объектом. В этом случае рекомендуется поместить этот код в try блок и поместить вызов Exit метода в finally блок. Это гарантирует снятие блокировки даже при возникновении исключения. Следующий фрагмент кода иллюстрирует этот шаблон.

// 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

Обычно это средство используется для синхронизации доступа к статическому методу или методу экземпляра класса.

Если критический раздел охватывает весь метод, средство блокировки можно достичь, поместив System.Runtime.CompilerServices.MethodImplAttribute Synchronized его в метод и указав значение в конструкторе System.Runtime.CompilerServices.MethodImplAttribute. При использовании этого атрибута Enter вызовы и Exit вызовы методов не требуются. Следующий фрагмент кода иллюстрирует этот шаблон:

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

Обратите внимание, что атрибут приводит к тому, что текущий поток удерживает блокировку до возврата метода; Если блокировка может быть освобождена раньше, используйте Monitor класс, оператор блокировки C# или оператор Visual Basic SyncLock внутри метода вместо атрибута.

Хотя это возможно для Enter операторов, Exit которые блокируют и освобождают заданный объект для пересечения границ членов или классов или обоих, эта практика не рекомендуется.

Pulse, PulseAll и Wait

После того как поток владеет блокировкой и вошел в критически важный раздел, который защищает блокировка, он может вызывать Monitor.WaitMonitor.Pulseметоды и , а Monitor.PulseAll также методы.

Когда поток, содержащий вызовы Waitблокировки, освобождается блокировка и поток добавляется в очередь ожидания синхронизированного объекта. Первый поток в очереди готовности, если таковой имеется, получает блокировку и входит в критический раздел. Вызываемый Wait поток перемещается из очереди ожидания в готовую очередь, когда Monitor.Pulse поток или Monitor.PulseAll метод вызывается потоком, который содержит блокировку (для перемещения поток должен находиться в голове очереди ожидания). Метод Wait возвращается, когда вызывающий поток повторно запрашивает блокировку.

Когда поток, содержащий вызовы Pulseблокировки, поток в голове очереди ожидания перемещается в готовую очередь. Вызов PulseAll метода перемещает все потоки из очереди ожидания в готовую очередь.

Мониторы и дескриптор ожидания

Важно отметить различие между использованием Monitor класса и WaitHandle объектов.

  • Класс Monitor полностью управляемый, полностью переносимый и может быть более эффективным с точки зрения требований к ресурсам операционной системы.

  • Объекты WaitHandle представляют объекты ожидания операционной системы, удобны для синхронизации между управляемым и неуправляемым кодом и предоставляют некоторые расширенные функции операционной системы, например возможность ожидания сразу нескольких объектов.

Свойства

LockContentionCount

Возвращает значение, указывающее, сколько раз возникало состязание при попытке установить блокировку монитора.

Методы

Enter(Object)

Получает эксклюзивную блокировку указанного объекта.

Enter(Object, Boolean)

Получает монопольную блокировку указанного объекта и единым блоком задает значение, указывающее, была ли выполнена блокировка.

Exit(Object)

Освобождает эксклюзивную блокировку указанного объекта.

IsEntered(Object)

Определяет, содержит ли текущий поток блокировку указанного объекта.

Pulse(Object)

Уведомляет поток в очереди готовности об изменении состояния объекта с блокировкой.

PulseAll(Object)

Уведомляет все ожидающие потоки об изменении состояния объекта.

TryEnter(Object)

Пытается получить эксклюзивную блокировку указанного объекта.

TryEnter(Object, Boolean)

Пытается получить монопольную блокировку указанного объекта и единым блоком задает значение, указывающее, была ли выполнена блокировка.

TryEnter(Object, Int32)

Пытается получить эксклюзивную блокировку указанного объекта на заданное количество миллисекунд.

TryEnter(Object, Int32, Boolean)

В течение заданного количества миллисекунд пытается получить монопольную блокировку указанного объекта и единым блоком задает значение, указывающее, была ли выполнена блокировка.

TryEnter(Object, TimeSpan)

Пытается получить эксклюзивную блокировку указанного объекта в течение заданного количества времени.

TryEnter(Object, TimeSpan, Boolean)

В течение заданного периода времени пытается получить монопольную блокировку указанного объекта и единым блоком задает значение, указывающее, была ли выполнена блокировка.

Wait(Object)

Освобождает блокировку объекта и блокирует текущий поток до тех пор, пока тот не получит блокировку снова.

Wait(Object, Int32)

Освобождает блокировку объекта и блокирует текущий поток до тех пор, пока тот не получит блокировку снова. Если указанные временные интервалы истекают, поток встает в очередь готовности.

Wait(Object, Int32, Boolean)

Освобождает блокировку объекта и блокирует текущий поток до тех пор, пока тот не получит блокировку снова. Если указанные временные интервалы истекают, поток встает в очередь готовности. Этот метод также указывает на выход из области синхронизации для контекста (если она находится в синхронизированном контексте) до ожидания и ее повторное получение впоследствии.

Wait(Object, TimeSpan)

Освобождает блокировку объекта и блокирует текущий поток до тех пор, пока тот не получит блокировку снова. Если указанные временные интервалы истекают, поток встает в очередь готовности.

Wait(Object, TimeSpan, Boolean)

Освобождает блокировку объекта и блокирует текущий поток до тех пор, пока тот не получит блокировку снова. Если указанные временные интервалы истекают, поток встает в очередь готовности. Дополнительно выходит из синхронизированного домена для синхронизации контекста до ожидания и получает домен впоследствии.

Применяется к

Потокобезопасность

Данный тип потокобезопасен.

См. также раздел