Monitor Monitor Monitor Monitor Class

Определение

Предоставляет механизм для синхронизации доступа к объектам.Provides a mechanism that synchronizes access to objects.

public ref class Monitor abstract sealed
[System.Runtime.InteropServices.ComVisible(true)]
public static class Monitor
type Monitor = class
Public Class Monitor
Наследование
MonitorMonitorMonitorMonitor
Атрибуты

Примеры

В следующем примере Monitor класс используется для синхронизации доступа к одному экземпляру генератора случайных чисел, представленного Random классом.The following example uses the Monitor class to synchronize access to a single instance of a random number generator represented by the Random class. В примере создается десять задач, каждый из которых асинхронно выполняется в потоке пула потоков.The example creates ten tasks, each of which executes asynchronously on a thread pool thread. Каждая задача создает 10 000 случайных чисел, вычисляет их среднее значение и обновляет две переменные уровня процедуры, которые сохраняют общую сумму количества созданных случайных чисел и их сумму.Each task generates 10,000 random numbers, calculates their average, and updates two procedure-level variables that maintain a running total of the number of random numbers generated and their sum. После выполнения всех задач эти два значения затем используются для вычисления общего значения.After all tasks have executed, these two values are then used to calculate the overall mean.

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 к переменным и также должен быть синхронизирован.Because they can be accessed from any task running on a thread pool thread, access to the variables total and n must also be synchronized. Для этой цели используется метод.Interlocked.AddThe Interlocked.Add method is used for this purpose.

В следующем примере показано Monitor комбинированное использование класса (реализованного lock с помощью AutoResetEvent конструкции языка SyncLock или), Interlocked класса и класса.The following example demonstrates the combined use of the Monitor class (implemented with the lock or SyncLock language construct), the Interlocked class, and the AutoResetEvent class. Он определяет два класса internal (в C#) или Friend (в Visual Basic), SyncResource и UnSyncResource, которые соответственно предоставляют синхронизированный и несинхронизированный доступ к ресурсу.It defines two internal (in C#) or Friend (in Visual Basic) classes, SyncResource and UnSyncResource, that respectively provide synchronized and unsynchronized access to a resource. Чтобы обеспечить демонстрацию в примере различия между синхронизированным и несинхронизированным доступом (что может случиться, если каждый вызов метода завершается быстро), метод включает случайную задержку: для потоков, свойство Thread.ManagedThreadId которых имеет четное значение, метод вызывает Thread.Sleep для введения задержки в 2 000 миллисекунд.To ensure that the example illustrates the difference between the synchronized and unsynchronized access (which could be the case if each method call completes rapidly), the method includes a random delay: for threads whose Thread.ManagedThreadId property is even, the method calls Thread.Sleep to introduce a delay of 2,000 milliseconds. Обратите внимание, что поскольку класс SyncResource не является общим, ни один клиентский код не выполняет блокировку в синхронизированном ресурсе; внутренний класс сам выполняет блокировку.Note that, because the SyncResource class is not public, none of the client code takes a lock on the synchronized resource; the internal class itself takes the lock. Это предотвращает блокировка общедоступного объекта вредоносным кодом.This prevents malicious code from taking a lock on a public object.

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 + vbNewLine + "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 + vbNewLine + "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, задающая число потоков, которые будут пытаться получить доступ к ресурсу.The example defines a variable, numOps, that defines the number of threads that will attempt to access the resource. Поток приложения вызывает метод ThreadPool.QueueUserWorkItem(WaitCallback) для синхронизированного и несинхронизированного доступа по пять раз.The application thread calls the ThreadPool.QueueUserWorkItem(WaitCallback) method for synchronized and unsynchronized access five times each. Метод ThreadPool.QueueUserWorkItem(WaitCallback) имеет единственный параметр, делегат, который не принимает никаких параметров и не возвращает значений.The ThreadPool.QueueUserWorkItem(WaitCallback) method has a single parameter, a delegate that accepts no parameters and returns no value. Для синхронизированного доступа он вызывает метод SyncUpdateResource; для несинхронизированного доступа он вызывает метод UnSyncUpdateResource.For synchronized access, it invokes the SyncUpdateResource method; for unsynchronized access, it invokes the UnSyncUpdateResource method. После каждого набора вызовов метода поток приложения вызывает метод AutoResetEvent. WaitOne , чтобы он блокировался до получения сигнала от AutoResetEvent экземпляра.After each set of method calls, the application thread calls the AutoResetEvent.WaitOne method so that it blocks until the AutoResetEvent instance is signaled.

Каждый вызов метода SyncUpdateResource вызывает внутренний метод SyncResource.Access, а затем вызывает метод Interlocked.Decrement для уменьшения счетчика numOps.Each call to the SyncUpdateResource method calls the internal SyncResource.Access method and then calls the Interlocked.Decrement method to decrement the numOps counter. Interlocked.Decrement Метод используется для уменьшения значения счетчика, так как в противном случае невозможно убедиться, что второй поток будет обращаться к значению до того, как значение декремента первого потока было сохранено в переменной.The Interlocked.Decrement method Is used to decrement the counter, because otherwise you cannot be certain that a second thread will access the value before a first thread's decremented value has been stored in the variable. Когда последний синхронизированный рабочий поток уменьшает счетчик до нуля, указывая, что все синхронизированные потоки завершили доступ к ресурсу, SyncUpdateResource метод EventWaitHandle.Set вызывает метод, который сигнализирует основному потоку продолжить. InstancePersistenceCommand.When the last synchronized worker thread decrements the counter to zero, indicating that all synchronized threads have completed accessing the resource, the SyncUpdateResource method calls the EventWaitHandle.Set method, which signals the main thread to continue execution.

Каждый вызов метода UnSyncUpdateResource вызывает внутренний метод UnSyncResource.Access, а затем вызывает метод Interlocked.Decrement для уменьшения счетчика numOps.Each call to the UnSyncUpdateResource method calls the internal UnSyncResource.Access method and then calls the Interlocked.Decrement method to decrement the numOps counter. Опять же, Interlocked.Decrement метод используется для уменьшения значения счетчика, чтобы второй поток не получил доступа к значению до того, как переменной будет присвоено уменьшенное значение первого потока.Once again, the Interlocked.Decrement method Is used to decrement the counter to ensure that a second thread does not access the value before a first thread's decremented value has been assigned to the variable. Когда последний несинхронизированный рабочий поток уменьшает значение счетчика до нуля, указывая, что больше несинхронизированных потоков не требуется обращаться к ресурсу, UnSyncUpdateResource метод EventWaitHandle.Set вызывает метод, который сигнализирует основному потоку продолжить выполнение. .When the last unsynchronized worker thread decrements the counter to zero, indicating that no more unsynchronized threads need to access the resource, the UnSyncUpdateResource method calls the EventWaitHandle.Set method, which signals the main thread to continue execution.

Как показывает результат этого примера, синхронизированный доступ обеспечивает, что вызывающий поток выходит из защищенного ресурса до того, как другой поток получит доступ к этому ресурсу; каждый поток ожидает своего предшественника.As the output from the example shows, synchronized access ensures that the calling thread exits the protected resource before another thread can access it; each thread waits on its predecessor. С другой стороны, без блокировки метод UnSyncResource.Access вызывается в том порядке, в котором потоки получают к нему доступ.On the other hand, without the lock, the UnSyncResource.Access method is called in the order in which threads reach it.

Комментарии

Класс позволяет синхронизировать доступ к области кода, Monitor.Enterвызывая и освобождая блокировку конкретного объекта путем вызова методов, Monitor.TryEnterи Monitor.Exit. MonitorThe Monitor class allows you to synchronize access to a region of code by taking and releasing a lock on a particular object by calling the Monitor.Enter, Monitor.TryEnter, and Monitor.Exit methods. Блокировки объектов предоставляют возможность ограничить доступ к блоку кода, обычно называемому критическим разделом.Object locks provide the ability to restrict access to a block of code, commonly called a critical section. Пока поток владеет блокировкой объекта, другой поток не может получить эту блокировку.While a thread owns the lock for an object, no other thread can acquire that lock. Можно также использовать Monitor класс, чтобы запретить другим потокам доступ к разделу кода приложения, выполняемому владельцем блокировки, если только другой поток не выполняет код, используя другой заблокированный объект.You can also use the Monitor class to ensure that no other thread is allowed to access a section of application code being executed by the lock owner, unless the other thread is executing the code using a different locked object.

Содержание этой статьиIn this article:

Класс Monitor: Обзор The Monitor class: An overview
Объект Lock The lock object
Критическая секция The critical section
Pulse, PulseAll и Wait Pulse, PulseAll, and Wait
Мониторы и дескрипторы ожиданияMonitors and wait handles

Класс Monitor: ОбзорThe Monitor class: An overview

Monitorимеет следующие возможности.Monitor has the following features:

  • Он связан с объектом по требованию.It is associated with an object on demand.

  • Он не связан. Это означает, что его можно вызывать непосредственно из любого контекста.It is unbound, which means it can be called directly from any context.

  • Экземпляр Monitor класса не может быть создан; методы Monitor класса являются статическими.An instance of the Monitor class cannot be created; the methods of the Monitor class are all static. Каждому методу передается синхронизированный объект, который управляет доступом к критической секции.Each method is passed the synchronized object that controls access to the critical section.

Примечание

Используйте класс для блокировки объектов, отличных от строк (то есть ссылочных типов, Stringотличных от), а не типов значений. MonitorUse the Monitor class to lock objects other than strings (that is, reference types other than String), not value types. Дополнительные сведения см. в разделе перегрузки Enter метода и объекта блокировки далее в этой статье.For details, see the overloads of the Enter method and The lock object section later in this article.

В следующей таблице описаны действия, которые могут выполняться потоками, обращающимися к синхронизированным объектам.The following table describes the actions that can be taken by threads that access synchronized objects:

ДействиеAction ОписаниеDescription
Enter, TryEnterEnter, TryEnter Получает блокировку для объекта.Acquires a lock for an object. Это действие также помечает начало критического раздела.This action also marks the beginning of a critical section. Ни один другой поток не может войти в критическую секцию, если он не будет выполнять инструкции в критическом разделе, используя другой заблокированный объект.No other thread can enter the critical section unless it is executing the instructions in the critical section using a different locked object.
Wait Освобождает блокировку объекта, чтобы позволить другим потокам блокировать и получать доступ к объекту.Releases the lock on an object in order to permit other threads to lock and access the object. Вызывающий поток ожидает, пока другой поток пообращается к объекту.The calling thread waits while another thread accesses the object. Импульсные сигналы используются для уведомления ожидающих потоков об изменениях в состоянии объекта.Pulse signals are used to notify waiting threads about changes to an object's state.
Pulse(сигнал),PulseAllPulse (signal), PulseAll Отправляет сигнал одному или нескольким ожидающим потокам.Sends a signal to one or more waiting threads. Сигнал уведомляет ожидающий поток о том, что состояние заблокированного объекта изменилось, а владелец блокировки готов освободить блокировку.The signal notifies a waiting thread that the state of the locked object has changed, and the owner of the lock is ready to release the lock. Ожидающий поток помещается в очередь готовности объекта, чтобы она могла в конечном итоге получить блокировку для объекта.The waiting thread is placed in the object's ready queue so that it might eventually receive the lock for the object. После блокировки потока он может проверить новое состояние объекта, чтобы узнать, было ли достигнуто требуемое состояние.Once the thread has the lock, it can check the new state of the object to see if the required state has been reached.
Exit Освобождает блокировку объекта.Releases the lock on an object. Это действие также помечает конец критической секции, защищенной заблокированным объектом.This action also marks the end of a critical section protected by the locked object.

Начиная с .NET Framework 4.NET Framework 4, существует два набора перегрузок Enter для методов и TryEnter .Beginning with the .NET Framework 4.NET Framework 4, there are two sets of overloads for the Enter and TryEnter methods. Один набор ref перегрузок имеет параметр (in C#) или ByRef (в Visual Basic) Boolean , который атомарно устанавливается в true значение, если блокировка получена, даже если при получении блокировки возникает исключение.One set of overloads has a ref (in C#) or ByRef (in Visual Basic) Boolean parameter that is atomically set to true if the lock is acquired, even if an exception is thrown when acquiring the lock. Используйте эти перегрузки, если важно снять блокировку во всех случаях, даже если ресурсы, защищающие блокировку, могут находиться в нестабильном состоянии.Use these overloads if it is critical to release the lock in all cases, even when the resources the lock is protecting might not be in a consistent state.

Объект LockThe lock object

Класс Monitor состоит из static методов (in C#) или Shared (в Visual Basic), которые работают с объектом, который управляет доступом к критическому разделу.The Monitor class consists of static (in C#) or Shared (in Visual Basic) methods that operate on an object that controls access to the critical section. Для каждого синхронизированного объекта сохраняются следующие сведения.The following information is maintained for each synchronized object:

  • Ссылка на поток, который в настоящий момент удерживает блокировку.A reference to the thread that currently holds the lock.

  • Ссылка на очередь готовности, которая содержит потоки, готовые к получению блокировки.A reference to a ready queue, which contains the threads that are ready to obtain the lock.

  • Ссылка на очередь ожидания, которая содержит потоки, ожидающие уведомления об изменении состояния заблокированного объекта.A reference to a waiting queue, which contains the threads that are waiting for notification of a change in the state of the locked object.

Monitor блокирует объекты (то есть ссылочные типы), а не типы значений.Monitor locks objects (that is, reference types), not value types. Хотя можно передать тип значения в Enter и Exit, он упаковывается отдельно для каждого вызова.While you can pass a value type to Enter and Exit, it is boxed separately for each call. Поскольку при каждом вызове создается отдельный объект, Enter никогда не выполняет блокировку, а код, который он предположительно защищает, на самом деле не синхронизируется.Since each call creates a separate object, Enter never blocks, and the code it is supposedly protecting is not really synchronized. Кроме того, объект, переданный в Exit, отличается от объекта, переданного в Enter, поэтому Monitor вызывает исключение SynchronizationLockException с сообщением «Для не синхронизированного блока кода вызван метод синхронизации объектов».In addition, the object passed to Exit is different from the object passed to Enter, so Monitor throws SynchronizationLockException exception with the message "Object synchronization method was called from an unsynchronized block of code."

Приведенный ниже пример иллюстрирует данную проблему.The following example illustrates this problem. Он запускает десять задач, каждая из которых просто бездействует в течение 250 миллисекунд.It launches ten tasks, each of which just sleeps for 250 milliseconds. Затем каждая задача обновляет переменную счетчика nTasks, который предназначен для подсчета количества фактически запущенных и выполненных задач.Each task then updates a counter variable, nTasks, which is intended to count the number of tasks that actually launched and executed. Поскольку nTasks является глобальной переменной, которая может обновляться несколькими задачами одновременно, используется монитор, защищающий ее от одновременного изменения несколькими задачами.Because nTasks is a global variable that can be updated by multiple tasks simultaneously, a monitor is used to protect it from simultaneous modification by multiple tasks. Тем не менее, как показывают выходные данные в примере, каждая из задач вызывает исключение SynchronizationLockException.However, as the output from the example shows, each of the tasks throws a SynchronizationLockException exception.

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 в каждой задаче.Each task throws a SynchronizationLockException exception because the nTasks variable is boxed before the call to the Monitor.Enter method in each task. Другими словами, в каждый вызов метода передается отдельная переменная, которая независима от остальных.In other words, each method call is passed a separate variable that is independent of the others. nTasks снова упаковывается в вызове метода Monitor.Exit.nTasks is boxed again in the call to the Monitor.Exit method. И снова при этом создается десять новых упакованных переменных, которые не зависят друг от друга, nTasks, и десять упакованных переменных, созданных при вызове метода Monitor.Enter.Once again, this creates ten new boxed variables, which are independent of each other, nTasks, and the ten boxed variables created in the call to the Monitor.Enter method. Затем вызывается исключение, поскольку наш код пытается снять блокировку для вновь созданной переменной, которая ранее не была заблокирована.The exception is thrown, then, because our code is attempting to release a lock on a newly created variable that was not previously locked.

Хотя можно упаковать переменную типа значения перед вызовом Enter и Exit, как показано в следующем примере, и передать тот же упакованный объект в оба метода, такой подход не дает никаких преимуществ.Although you can box a value type variable before calling Enter and Exit, as shown in the following example, and pass the same boxed object to both methods, there is no advantage to doing this. Изменения неупакованной переменной не отражаются в упакованной копии, и возможность изменения значения упакованной копии отсутствует.Changes to the unboxed variable are not reflected in the boxed copy, and there is no way to change the value of the boxed copy.

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.

При выборе объекта для синхронизации следует блокировать только закрытые или внутренние объекты.When selecting an object on which to synchronize, you should lock only on private or internal objects. Блокировка внешних объектов может привести к взаимоблокировкам, так как несвязанный код может выбирать те же объекты для блокировки в различных целях.Locking on external objects might result in deadlocks, because unrelated code could choose the same objects to lock on for different purposes.

Обратите внимание, что можно выполнить синхронизацию для объекта в нескольких доменах приложений, если объект, используемый для MarshalByRefObjectблокировки, является производным от.Note that you can synchronize on an object in multiple application domains if the object used for the lock derives from MarshalByRefObject.

Критическая секцияThe critical section

Используйте методы Exit и для обозначения начала и конца критической секции. EnterUse the Enter and Exit methods to mark the beginning and end of a critical section.

Примечание

Функциональные возможности, предоставляемые Enter Exit методами и, идентичны функциям, предоставляемым оператором C# Lock в и оператором SyncLock в Visual Basic, за исключением того, что языковые конструкции заключают Monitor.Enter(Object, Boolean)перегрузка метода и Monitor.Exit tryметод в...finallyThe functionality provided by the Enter and Exit methods is identical to that provided by the lock statement in C# and the SyncLock statement in Visual Basic, except that the language constructs wrap the Monitor.Enter(Object, Boolean) method overload and the Monitor.Exit method in a tryfinally , чтобы убедиться, что монитор освобожден.block to ensure that the monitor is released.

Если критическая секция является набором смежных инструкций, то блокировка, полученная Enter методом, гарантирует, что только один поток может выполнить вложенный код с заблокированным объектом.If the critical section is a set of contiguous instructions, then the lock acquired by the Enter method guarantees that only a single thread can execute the enclosed code with the locked object. В этом случае рекомендуется поместить этот код в try блок и поместить вызов Exit метода в finally блок.In this case, we recommend that you place that code in a try block and place the call to the Exit method in a finally block. Это гарантирует снятие блокировки даже при возникновении исключения.This ensures that the lock is released even if an exception occurs. Этот шаблон показан в следующем фрагменте кода.The following code fragment illustrates this pattern.

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

Это средство обычно используется для синхронизации доступа к статическому методу или экземпляру класса.This facility is typically used to synchronize access to a static or instance method of a class.

Если критическая секция охватывает весь метод, механизм блокировки можно достичь, поместив в System.Runtime.CompilerServices.MethodImplAttribute метод и Synchronized указав System.Runtime.CompilerServices.MethodImplAttributeзначение в конструкторе.If a critical section spans an entire method, the locking facility can be achieved by placing the System.Runtime.CompilerServices.MethodImplAttribute on the method, and specifying the Synchronized value in the constructor of System.Runtime.CompilerServices.MethodImplAttribute. При использовании этого атрибута Enter вызовы методов и Exit не требуются.When you use this attribute, the Enter and Exit method calls are not needed. Этот шаблон показан в следующем фрагменте кода:The following code fragment illustrates this pattern:

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

Обратите внимание, что атрибут заставляет текущий поток удерживать блокировку до тех пор, пока метод не вернет значение. Если блокировку можно освободить Monitor раньше, используйте класс, C# оператор блокировки или оператор Visual Basic SyncLock внутри метода вместо атрибута.Note that the attribute causes the current thread to hold the lock until the method returns; if the lock can be released sooner, use the Monitor class, the C# lock statement, or the Visual Basic SyncLock statement inside of the method instead of the attribute.

Хотя для Enter операторов и Exit , которые блокируют и освобождают данный объект, можно использовать перекрестные элементы или границы класса или и то, и другое, такой подход не рекомендуется.While it is possible for the Enter and Exit statements that lock and release a given object to cross member or class boundaries or both, this practice is not recommended.

Pulse, PulseAll и WaitPulse, PulseAll, and Wait

После того как поток владеет блокировкой и вошел в критический раздел, который защищается блокировкой, он может вызывать Monitor.Waitметоды Monitor.Pulse, и Monitor.PulseAll .Once a thread owns the lock and has entered the critical section that the lock protects, it can call the Monitor.Wait, Monitor.Pulse, and Monitor.PulseAll methods.

Когда поток, содержащий вызовы Waitблокировки, снимается и поток добавляется в очередь ожидания синхронизированного объекта.When the thread that holds the lock calls Wait, the lock is released and the thread is added to the waiting queue of the synchronized object. Первый поток в очереди готовности, если таковой имеется, получает блокировку и вводит критическую секцию.The first thread in the ready queue, if any, acquires the lock and enters the critical section. Поток, вызываемый Wait , перемещается из очереди ожидания в очередь готовности, Monitor.Pulse если Monitor.PulseAll метод или вызывается потоком, который владеет блокировкой (для перемещения, поток должен находиться в заголовке очереди ожидания).The thread that called Wait is moved from the waiting queue to the ready queue when either the Monitor.Pulse or the Monitor.PulseAll method is called by the thread that holds the lock (to be moved, the thread must be at the head of the waiting queue). Wait Метод возвращает, когда вызывающий поток снова получает блокировку.The Wait method returns when the calling thread reacquires the lock.

При вызове потока, содержащего вызовы Pulseблокировки, поток в заголовке очереди ожидания перемещается в очередь готовности.When the thread that holds the lock calls Pulse, the thread at the head of the waiting queue is moved to the ready queue. Вызов PulseAll метода перемещает все потоки из очереди ожидания в очередь готовности.The call to the PulseAll method moves all the threads from the waiting queue to the ready queue.

Мониторы и дескрипторы ожиданияMonitors and wait handles

Важно отметить различие между использованием Monitor класса и WaitHandle объектов.It is important to note the distinction between the use of the Monitor class and WaitHandle objects.

  • Monitor Класс является полностью управляемым, полным переносимым и может быть более эффективным с точки зрения требований к ресурсам операционной системы.The Monitor class is purely managed, fully portable, and might be more efficient in terms of operating-system resource requirements.

  • Объекты WaitHandle представляют объекты ожидания операционной системы, удобны для синхронизации между управляемым и неуправляемым кодом и предоставляют некоторые расширенные функции операционной системы, например возможность ожидания сразу нескольких объектов.WaitHandle objects represent operating-system waitable objects, are useful for synchronizing between managed and unmanaged code, and expose some advanced operating-system features like the ability to wait on many objects at once.

Свойства

LockContentionCount LockContentionCount LockContentionCount LockContentionCount

Возвращает значение, указывающее, сколько раз возникало состязание при попытке установить блокировку монитора.Gets the number of times there was contention when trying to take the monitor's lock.

Методы

Enter(Object) Enter(Object) Enter(Object) Enter(Object)

Получает эксклюзивную блокировку указанного объекта.Acquires an exclusive lock on the specified object.

Enter(Object, Boolean) Enter(Object, Boolean) Enter(Object, Boolean) Enter(Object, Boolean)

Получает монопольную блокировку указанного объекта и единым блоком задает значение, указывающее, была ли выполнена блокировка.Acquires an exclusive lock on the specified object, and atomically sets a value that indicates whether the lock was taken.

Exit(Object) Exit(Object) Exit(Object) Exit(Object)

Освобождает эксклюзивную блокировку указанного объекта.Releases an exclusive lock on the specified object.

IsEntered(Object) IsEntered(Object) IsEntered(Object) IsEntered(Object)

Определяет, является ли содержит ли текущий поток блокировку на заданном объекте.Determines whether the current thread holds the lock on the specified object.

Pulse(Object) Pulse(Object) Pulse(Object) Pulse(Object)

Уведомляет поток в очереди готовности об изменении состояния объекта с блокировкой.Notifies a thread in the waiting queue of a change in the locked object's state.

PulseAll(Object) PulseAll(Object) PulseAll(Object) PulseAll(Object)

Уведомляет все ожидающие потоки об изменении состояния объекта.Notifies all waiting threads of a change in the object's state.

TryEnter(Object, TimeSpan, Boolean) TryEnter(Object, TimeSpan, Boolean) TryEnter(Object, TimeSpan, Boolean) TryEnter(Object, TimeSpan, Boolean)

В течение заданного периода времени пытается получить монопольную блокировку указанного объекта и единым блоком задает значение, указывающее, была ли выполнена блокировка.Attempts, for the specified amount of time, to acquire an exclusive lock on the specified object, and atomically sets a value that indicates whether the lock was taken.

TryEnter(Object, Int32, Boolean) TryEnter(Object, Int32, Boolean) TryEnter(Object, Int32, Boolean) TryEnter(Object, Int32, Boolean)

В течение заданного количества миллисекунд пытается получить монопольную блокировку указанного объекта и единым блоком задает значение, указывающее, была ли выполнена блокировка.Attempts, for the specified number of milliseconds, to acquire an exclusive lock on the specified object, and atomically sets a value that indicates whether the lock was taken.

TryEnter(Object, TimeSpan) TryEnter(Object, TimeSpan) TryEnter(Object, TimeSpan) TryEnter(Object, TimeSpan)

Пытается получить эксклюзивную блокировку указанного объекта в течение заданного количества времени.Attempts, for the specified amount of time, to acquire an exclusive lock on the specified object.

TryEnter(Object, Boolean) TryEnter(Object, Boolean) TryEnter(Object, Boolean) TryEnter(Object, Boolean)

Пытается получить монопольную блокировку указанного объекта и единым блоком задает значение, указывающее, была ли выполнена блокировка.Attempts to acquire an exclusive lock on the specified object, and atomically sets a value that indicates whether the lock was taken.

TryEnter(Object) TryEnter(Object) TryEnter(Object) TryEnter(Object)

Пытается получить эксклюзивную блокировку указанного объекта.Attempts to acquire an exclusive lock on the specified object.

TryEnter(Object, Int32) TryEnter(Object, Int32) TryEnter(Object, Int32) TryEnter(Object, Int32)

Пытается получить эксклюзивную блокировку указанного объекта на заданное количество миллисекунд.Attempts, for the specified number of milliseconds, to acquire an exclusive lock on the specified object.

Wait(Object, Int32, Boolean) Wait(Object, Int32, Boolean) Wait(Object, Int32, Boolean) Wait(Object, Int32, Boolean)

Освобождает блокировку объекта и блокирует текущий поток до тех пор, пока тот не получит блокировку снова.Releases the lock on an object and blocks the current thread until it reacquires the lock. Если указанные временные интервалы истекают, поток встает в очередь готовности.If the specified time-out interval elapses, the thread enters the ready queue. Этот метод также указывает на наличие завершения области синхронизации для контекста (если в синхронизированном контексте) до получения впоследствии нового состояния ожидания.This method also specifies whether the synchronization domain for the context (if in a synchronized context) is exited before the wait and reacquired afterward.

Wait(Object) Wait(Object) Wait(Object) Wait(Object)

Освобождает блокировку объекта и блокирует текущий поток до тех пор, пока тот не получит блокировку снова.Releases the lock on an object and blocks the current thread until it reacquires the lock.

Wait(Object, Int32) Wait(Object, Int32) Wait(Object, Int32) Wait(Object, Int32)

Освобождает блокировку объекта и блокирует текущий поток до тех пор, пока тот не получит блокировку снова.Releases the lock on an object and blocks the current thread until it reacquires the lock. Если указанные временные интервалы истекают, поток встает в очередь готовности.If the specified time-out interval elapses, the thread enters the ready queue.

Wait(Object, TimeSpan) Wait(Object, TimeSpan) Wait(Object, TimeSpan) Wait(Object, TimeSpan)

Освобождает блокировку объекта и блокирует текущий поток до тех пор, пока тот не получит блокировку снова.Releases the lock on an object and blocks the current thread until it reacquires the lock. Если указанные временные интервалы истекают, поток встает в очередь готовности.If the specified time-out interval elapses, the thread enters the ready queue.

Wait(Object, TimeSpan, Boolean) Wait(Object, TimeSpan, Boolean) Wait(Object, TimeSpan, Boolean) Wait(Object, TimeSpan, Boolean)

Освобождает блокировку объекта и блокирует текущий поток до тех пор, пока тот не получит блокировку снова.Releases the lock on an object and blocks the current thread until it reacquires the lock. Если указанные временные интервалы истекают, поток встает в очередь готовности.If the specified time-out interval elapses, the thread enters the ready queue. Дополнительно выходит из синхронизированного домена для синхронизации контекста до ожидания и получает домен впоследствии.Optionally exits the synchronization domain for the synchronized context before the wait and reacquires the domain afterward.

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

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

Данный тип потокобезопасен.This type is thread safe.

Дополнительно