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)

由于可以从线程池线程上运行的任何任务访问它们,因此还必须同步访问变量 totaln 。 方法 Interlocked.Add 用于此目的。

以下示例演示类 (与 lockSyncLock 语言构造) 、Interlocked类和 类实现的组合AutoResetEvent用法Monitor。 它定义了两个 internal(在 C# 中)或 Friend(在 Visual Basic)类(SyncResourceUnSyncResource),它们分别提供对资源的同步和不同步访问。 为了确保此示例展示同步和不同步访问的差异(如果每个方法调用快速完成,可能出现这种情况),此方法包含一次随机延迟:对于其 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 方法,以指示main线程继续执行。

每次调用 UnSyncUpdateResource 方法都会调用内部 UnSyncResource.Access 方法,然后调用 Interlocked.Decrement 方法以递减 numOps 计数器。 同样, Interlocked.Decrement 方法用于递减计数器,以确保在第一个线程的递减值分配给变量之前,第二个线程不会访问该值。 当最后一个未同步的工作线程将计数器递减为零(指示不再需要未同步的线程访问资源)时UnSyncUpdateResource,该方法将调用 EventWaitHandle.Set 方法,以指示main线程继续执行。

如示例输出所示,同步访问可确保调用线程退出受保护的资源,然后另一个线程可以访问它;每个线程都在等待其前置任务。 另一方面,在不采用锁的情况下,将按线程到达的顺序调用 UnSyncResource.Access 方法。

注解

Monitor 允许你通过调用 Monitor.EnterMonitor.TryEnterMonitor.Exit 方法获取并释放特定对象的锁来同步对代码区域的访问。 使用对象锁可以限制对代码块(通常称为关键部分)的访问。 虽然线程拥有对象的锁,但其他线程无法获取该锁。 还可以使用 Monitor 类来确保不允许其他线程访问锁所有者正在执行的应用程序代码部分,除非另一个线程使用其他锁定对象执行代码。 由于 Monitor 类具有线程相关性,因此获取锁的线程必须通过调用 Monitor.Exit 方法释放锁。

本文内容:

Monitor 类:概述
lock 对象
关键部分
Pulse、PulseAll 和 Wait
监视和等待句柄

Monitor 类:概述

Monitor 具有以下功能:

  • 它与按需对象相关联。

  • 它是未绑定的,这意味着可以直接从任何上下文调用它。

  • 无法创建 类的 Monitor 实例;类的方法都是静态的 Monitor 。 每个方法都传递同步对象,该对象控制对关键节的访问。

注意

Monitor使用 类可锁定字符串以外的对象 (即引用类型而不是String) ,而不是值类型。 有关详细信息,请参阅本文后面的 方法的重载 Enter锁对象 部分。

下表描述了访问同步对象的线程可以执行的操作:

操作 说明
Enter, TryEnter 获取对象的锁。 此操作还标记关键部分的开头。 任何其他线程都不能进入关键节,除非它使用不同的锁定对象执行关键节中的指令。
Wait 释放对象上的锁,以允许其他线程锁定和访问对象。 调用线程等待,而另一个线程访问对象。 脉冲信号用于通知等待线程对对象状态的更改。
Pulse (信号) , PulseAll 向一个或多个等待线程发送信号。 该信号通知等待线程锁定对象的状态已更改,并且锁的所有者已准备好释放锁。 等待线程放置在对象的就绪队列中,以便它最终可能会收到对象的锁。 线程具有锁后,可以检查对象的新状态,以查看是否已达到所需的状态。
Exit 释放对象上的锁。 此操作还标记受锁定对象保护的关键节的末尾。

从 .NET Framework 4 开始,和 方法有两组重载EnterTryEnter。 一组重载在 C#) 或 ByRef Visual Basic) Boolean 参数中 (ref (,即使获取锁时引发异常,该参数也以原子方式设置为 true 。 如果在所有情况下释放锁至关重要,则使用这些重载,即使锁保护的资源可能处于不一致状态也是如此。

lock 对象

Monitor 类由 C#) 中的 (或 Shared Visual Basic) 方法中的 (组成static,这些方法对控制对关键节的访问的对象进行操作。 将为每个同步对象保留以下信息:

  • 对当前持有锁的线程的引用。

  • 对就绪队列的引用,该队列包含已准备好获取锁的线程。

  • 对等待队列的引用,该队列包含正在等待锁定对象状态更改通知的线程。

Monitor 锁定对象(即引用类型),而不是值类型。 虽然可以将值类型传递到 EnterExit,但对每个调用它都分别进行了装箱。 由于每次调用都将创建一个单独的对象,所以绝不会阻止 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.

因为 nTasks 变量会先于对每个任务中的 Monitor.Enter 方法的调用而进行装箱,所以每个任务都将引发 SynchronizationLockException 异常。 换言之,将向每个方法调用传递一个独立于其他变量的单独变量。 nTasks 在对 Monitor.Exit 方法的调用中会再次进行装箱。 这将再次创建 10 个新装箱的变量 nTasks,这些变量相互独立并且独立于在调用 Monitor.Enter 方法时创建的 10 个装箱变量。 然后将引发异常,因为我们的代码正尝试在之前未锁定的新创建变量上释放锁定。

尽管可以先装箱值类型变量,再调用 EnterExit(如下例所示)并且同时向这两种方法传递相同的装箱对象,但是进行该操作并没有什么好处。 对未装箱的变量所做的更改不会反映在装箱副本中,并且无法更改装箱副本的值。

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 方法标记关键部分的开头和结尾。

注意

Exit 方法提供Enter的功能与 C# 中的 lock 语句和 Visual Basic 中的 SyncLock 语句提供的功能相同,只不过语言构造将方法重载和 Monitor.Exit 方法包装Monitor.Enter(Object, Boolean)在 ... 中tryfinally 阻止以确保释放监视器。

如果关键部分是一组连续指令,则方法获取 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 ,并在 的System.Runtime.CompilerServices.MethodImplAttribute构造函数中指定Synchronized值来实现锁定设施。 使用此属性时, Enter 不需要 和 Exit 方法调用。 以下代码片段演示了此模式:

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

请注意, 属性会导致当前线程持有锁,直到方法返回;如果可以更快地释放锁,请在 Monitor 方法中使用 类、C# lock 语句或 Visual Basic SyncLock 语句,而不是 属性。

虽然锁定和释放给定对象的 和 Exit 语句可以Enter跨越成员或类边界或两者,但不建议这样做。

Pulse、PulseAll 和 Wait

线程拥有锁并进入锁保护的关键部分后,就可以调用 Monitor.WaitMonitor.PulseMonitor.PulseAll 方法。

当持有锁的线程调用 Wait时,将释放锁,并将该线程添加到同步对象的等待队列中。 就绪队列中的第一个线程(如果有)获取锁并进入关键部分。 当持有要移动的锁的线程Wait调用 或 Monitor.PulseAll 方法时Monitor.Pulse,调用的线程将从等待队列移动到就绪队列, (线程必须位于等待队列) 的头部。 当 Wait 调用线程重新获取锁时, 方法返回 。

当持有锁的线程调用 Pulse时,位于等待队列头部的线程将移动到就绪队列。 PulseAll调用 方法会将所有线程从等待队列移动到就绪队列。

监视和等待句柄

请务必注意类和WaitHandle对象的用法Monitor之间的区别。

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

释放对象上的锁并阻止当前线程,直到它重新获取该锁。 如果已用指定的超时时间间隔,则线程进入就绪队列。 可以在等待之前退出同步上下文的同步域,随后重新获取该域。

适用于

线程安全性

此类型是线程安全的。

另请参阅