System.Threading.Monitor 类

本文提供了此 API 参考文档的补充说明。

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

概述

Monitor 具有以下功能:

  • 它与按需对象相关联。
  • 它是未绑定的,这意味着可以直接从任何上下文调用它。
  • 无法创建类的 Monitor 实例;类的方法都是静态的 Monitor 。 每个方法都传递同步的对象,该对象控制对关键节的访问。

注意

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

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

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

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

lock 对象

Monitor 类包含 staticShared 在 Visual Basic 中)方法,这些方法对控制对关键部分的访问的对象进行操作。 为每个同步对象维护以下信息:

  • 对当前持有锁的线程的引用。
  • 对就绪队列的引用,其中包含准备获取锁的线程。
  • 对等待队列的引用,其中包含等待通知锁定对象状态更改的线程。

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 Example1
{
    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 Example3
    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 Example2
    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方法标记关键节的开始和结尾。

注意

EnterExit方法提供的功能与 C# 中的 lock 语句和 Visual Basic 中的 SyncLock 语句所提供的功能相同,只是语言构造Monitor.Enter(Object, Boolean)包装方法重载和Monitor.Exit方法在 ...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 方法上并指定 SynchronizedSystem.Runtime.CompilerServices.MethodImplAttribute构造函数中的值来实现锁定设施。 使用此属性时, 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.PulseMonitor.PulseAll,将调用的线程从等待队列移动到就绪队列。 此方法 Wait 在调用线程重新获取锁时返回。

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

监视和等待句柄

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

  • Monitor 类纯粹是托管的、完全可移植的,在操作系统资源要求方面可能更高效。
  • WaitHandle 对象表示操作系统可等待对象,有助于托管和非托管代码之间进行同步,并公开一些高级操作系统功能,如同时等待多个对象的功能。

示例

以下示例使用 Monitor 类来同步对由该类表示 Random 的随机数生成器的单个实例的访问。 该示例创建 10 个任务,每个任务在线程池线程上异步执行。 每个任务生成 10,000 个随机数,计算其平均值,并更新两个过程级变量,这些变量维护生成的随机数总数及其总和。 执行所有任务后,然后使用这两个值计算总体平均值。

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example2
{
    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 Example4
    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 用于此目的。

下面的示例演示类(通过lock或语言构造实现)、Interlocked类和AutoResetEvent类的组合用法MonitorSyncLock。 它定义了两个 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 该方法,该方法指示主线程继续执行。

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

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