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Метод используется для уменьшения значения счетчика, так как в противном случае невозможно убедиться, что второй поток будет обращаться к значению до того, как значение декремента первого потока было сохранено в переменной. Когда последний синхронизированный рабочий поток уменьшает счетчик до нуля, указывая, что все синхронизированные потоки завершили доступ к ресурсу, SyncUpdateResource метод вызывает EventWaitHandle.Set метод, который сигнализирует основному потоку продолжить выполнение.

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

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

Комментарии

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

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

Обзор класса Monitor.
Объект Lock
Критическая секция
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 если блокировка получена, даже если при получении блокировки возникает исключение. Используйте эти перегрузки, если важно снять блокировку во всех случаях, даже если ресурсы, защищающие блокировку, могут находиться в нестабильном состоянии.

Объект Lock

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

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

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

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

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 , чтобы убедиться, что монитор освобожден.

Если критическая секция является набором смежных инструкций, то блокировка, полученная 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.Wait Monitor.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)

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

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

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

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

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