Monitor 类

定义

提供同步访问对象的机制。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
继承
Monitor
属性

示例

下面的示例使用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. 每个任务都生成10000个随机数字,计算其平均值,并更新两个过程级别的变量,这些变量维护生成的随机数的总数及其总和。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.Add方法用于实现此目的。The Interlocked.Add method is used for this purpose.

下面Monitor的示例演示了类的组合用法( lock用或SyncLock语言AutoResetEvent构造实现)、 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)类(SyncResourceUnSyncResource),它们分别提供对资源的同步和不同步访问。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方法将调用方法,该方法指示主线程继续操作.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
脉冲、System.threading.monitor.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
EnterTryEnterEnter, 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.

从开始Enter TryEnter ,有两组重载用于和方法。 .NET Framework 4.NET Framework 4Beginning with the .NET Framework 4.NET Framework 4, there are two sets of overloads for the Enter and TryEnter methods. ref组重载具有一个(in)或ByRef (在 Visual Basic) Boolean参数,如果已获取锁, true则该参数以原子方式C#设置为,即使在获取锁时引发了异常。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.

Lock 对象The lock object

Monitor 类由static (在中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. 虽然可以将值类型传递到 EnterExit,但对每个调用它都分别进行了装箱。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.

因为 nTasks 变量会先于对每个任务中的 Monitor.Enter 方法的调用而进行装箱,所以每个任务都将引发 SynchronizationLockException 异常。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. 这将再次创建 10 个新装箱的变量 nTasks,这些变量相互独立并且独立于在调用 Monitor.Enter 方法时创建的 10 个装箱变量。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.

尽管可以先装箱值类型变量,再调用 EnterExit(如下例所示)并且同时向这两种方法传递相同的装箱对象,但是进行该操作并没有什么好处。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

Enter使用和Exit方法来标记临界区的开头和结尾。Use the Enter and Exit methods to mark the beginning and end of a critical section.

备注

Enter C# 和方法Exit提供的功能与 Visual Basic 中的 lock 语句提供的功能相同,但 language 构造包装了Monitor.Enter(Object, Boolean)方法重载和中Monitor.Exittry方法 .。。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#类、 lock语句或 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.

脉冲、System.threading.monitor.pulseall 和 WaitPulse, PulseAll, and Wait

一旦某个线程拥有该锁并且进入了锁保护的临界区,它就可以调用Monitor.WaitMonitor.PulseMonitor.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.PulseAll或方法由持有锁的线程调用,则调用的线程将从等待队列移到就绪队列,而线程必须处于等待队列的开头。 Monitor.PulseThe 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

获取尝试锁定监视器时出现争用的次数。Gets the number of times there was contention when trying to take the monitor's lock.

方法

Enter(Object)

在指定对象上获取排他锁。Acquires an exclusive lock on the specified object.

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)

释放指定对象上的排他锁。Releases an exclusive lock on the specified object.

IsEntered(Object)

确定当前线程是否保留指定对象锁。Determines whether the current thread holds the lock on the specified object.

Pulse(Object)

通知等待队列中的线程锁定对象状态的更改。Notifies a thread in the waiting queue of a change in the locked object's state.

PulseAll(Object)

通知所有的等待线程对象状态的更改。Notifies all waiting threads of a change in the object's state.

TryEnter(Object)

尝试获取指定对象的排他锁。Attempts to acquire an exclusive lock on the specified object.

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, Int32)

在指定的毫秒数内尝试获取指定对象上的排他锁。Attempts, for the specified number of milliseconds, to acquire an exclusive lock on the specified object.

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)

在指定的时间内尝试获取指定对象上的排他锁。Attempts, for the specified amount of time, to acquire an exclusive lock on the specified object.

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.

Wait(Object)

释放对象上的锁并阻止当前线程,直到它重新获取该锁。Releases the lock on an object and blocks the current thread until it reacquires the lock.

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

释放对象上的锁并阻止当前线程,直到它重新获取该锁。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.

另请参阅