System.Threading.Monitor 類別

本文提供此 API 參考文件的補充備註。

類別 Monitor 可讓您藉由呼叫 Monitor.EnterMonitor.TryEnterMonitor.Exit 方法來擷取並釋放特定對象的鎖定,以同步存取程式代碼區域。 對象鎖定可讓您限制對程式碼區塊的存取,通常稱為重要區段。 雖然線程擁有對象的鎖定,但沒有其他線程可以取得該鎖定。 您也可以使用 類別 Monitor 來確保沒有任何其他線程可以存取鎖定擁有者所執行的應用程式程式代碼區段,除非其他線程使用不同的鎖定物件執行程序代碼。 因為 Monitor 類別具有線程親和性,因此取得鎖定的線程必須藉由呼叫 Monitor.Exit 方法來釋放鎖定。

概觀

Monitor 具有下列功能:

  • 它會隨選與 對象相關聯。
  • 它是未系結的,這表示可以直接從任何內容呼叫它。
  • 無法建立 類別的實例;類別的方法MonitorMonitor都是靜態的。 每個方法都會傳遞同步處理的物件,以控制對重要區段的存取。

注意

使用 類別 Monitor 來鎖定字串以外的物件(也就是以外的 String參考型別),而不是實值型別。 如需詳細資訊,請參閱本文稍後的 Enter 方法多載和 Lock對象 一節。

下表描述可讓存取同步物件之線程採取的動作:

動作 描述
Enter, TryEnter 取得對象的鎖定。 此動作也會標示重要區段的開頭。 除非其他線程使用不同的鎖定物件執行重要區段中的指示,否則沒有其他線程可以進入關鍵區段。
Wait 釋放對象的鎖定,以允許其他線程鎖定和存取物件。 呼叫線程會在另一個線程存取 物件時等候。 脈衝訊號可用來通知等候線程有關對象狀態的變更。
Pulse (訊號), PulseAll 將訊號傳送至一或多個等候的線程。 訊號會通知等候線程鎖定物件的狀態已變更,而鎖定的擁有者已準備好釋放鎖定。 等候的線程會放在物件的就緒佇列中,以便最終接收對象的鎖定。 線程鎖定之後,即可檢查 物件的新狀態,以查看是否已達到所需的狀態。
Exit 釋放物件上的鎖定。 此動作也會標示鎖定物件所保護之重要區段的結尾。

TryEnter 方法有兩組多載Enter。 一組多載具有 ref (在 C# 中) 或 ByRef (在 Visual Basic 中) Boolean 參數,如果取得鎖定,則會以不可部分完成的方式設定為 true ,即使取得鎖定時擲回例外狀況也一樣。 如果在所有情況下釋放鎖定很重要,請使用這些多載,即使鎖定保護的資源可能不是處於一致狀態也一樣。

lock 物件

Monitor 類別是由 static 在控制重要區段存取的物件上運作的 (Shared 在 Visual Basic 中) 方法所組成。 每個同步處理的物件都會維護下列資訊:

  • 目前保留鎖定之線程的參考。
  • 就緒佇列的參考,其中包含準備好取得鎖定的線程。
  • 等候佇列的參考,其中包含正在等候鎖定對象狀態變更通知的線程。

Monitor 會鎖定物件 (也就是參考類型),而不會鎖定值類型。 雖然您可以傳遞值類型到 EnterExit,它會個別針對每個呼叫進行 boxed 處理。 因為每個呼叫會建立不同的物件,Enter 絕不會封鎖,且它應該要保護的程式碼不會真地同步處理。 此外,傳遞給 Exit 的物件不同於傳遞給 Enter 的物件,因此 Monitor 會擲回 SynchronizationLockException 例外狀況,以及訊息「從未同步處理的程式碼區塊呼叫物件同步化方法」。

下列範例說明此問題。 它會啟動 10 個工作,其中每個工作睡眠 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.

每個工作都擲回 SynchronizationLockException 例外狀況是因為 nTasks 變數在呼叫每個工作中的 Monitor.Enter 方法之前會進行 boxed 處理。 換句話說,每個方法呼叫都被傳遞個別的變數,且與其他變數無關。 nTasks 在呼叫 Monitor.Exit 方法時會再次進行 boxed 處理。 同樣地,這會建立十個彼此獨立的新 boxed 變數 nTasks,以及在呼叫 Monitor.Enter 方法時建立的十個 boxed 變數。 接著便擲回例外狀況,因為我們的程式碼嘗試對於先前未鎖定的新建變數釋放鎖定。

雖然您可以先將值類型變數進行 box 處理,然後再呼叫 EnterExit,如下列範例所示,並將相同的 boxed 物件傳遞給這兩種方法,但這麼做並沒有任何益處。 變更 unboxed 變數不會反映在 boxed 複本,且沒有任何方法可變更 boxed 複本的值。

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 方法來標記重要區段的開頭和結尾。

注意

和方法所提供的Enter功能與 C# 中的lock語句和 Visual Basic 中的 SyncLock 語句所提供的功能相同,不同之處在於語言建構會將方法多載和 Monitor.Exit 方法包裝Monitor.Enter(Object, Boolean)try...Exitfinally 封鎖 以確保監視器已釋放。

如果關鍵區段是一組連續指令,則方法取得 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時,會釋放鎖定,並將線程新增至同步處理物件的等候佇列。 如果有任何的話,就緒佇列中的第一個線程會取得鎖定,並進入關鍵區段。 當 保留鎖定的線程呼叫 或 Monitor.PulseAll 方法時Monitor.Pulse,呼叫的線程Wait會從等候佇列移至就緒佇列(要移動,線程必須位於等候佇列的前端)。 方法 Wait 會在呼叫線程重新取得鎖定時傳回。

當保留鎖定的線程呼叫 Pulse時,等候佇列前端的線程會移至就緒佇列。 方法的 PulseAll 呼叫會將所有線程從等候佇列移至就緒佇列。

監視和等候句柄

請務必注意類別和 WaitHandle 物件使用Monitor之間的差異。

  • 類別 Monitor 完全受控、完全可攜,而且在操作系統資源需求方面可能更有效率。
  • WaitHandle 物件代表作業系統可等候物件、適用於 managed 和 unmanaged 程式碼之間的同步處理,並且公開一些進階的作業系統功能,例如一次等候許多物件的能力。

範例

下列範例會 Monitor 使用 類別來同步存取 類別所 Random 代表之隨機數產生器的單一實例。 此範例會建立十個工作,每個工作會在線程集區線程上以異步方式執行。 每個工作都會產生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 會用於此用途。

下列範例示範 類別的結合用法 Monitor (使用 lockSyncLock 語言建構實作)、 Interlocked 類別和 AutoResetEvent 類別。 它會定義兩個 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 方法 Is 用來遞減計數器,以確保第二個線程不會在第一個線程遞減值指派給變數之前存取值。 當最後一個未同步處理的背景工作線程將計數器遞減為零時,表示不再有未同步處理的線程需要存取資源, UnSyncUpdateResource 此方法會呼叫 EventWaitHandle.Set 方法,這個方法會指示主線程繼續執行。

如範例輸出所示,同步存取可確保呼叫執行緒會先結束受保護的資源,其他執行緒才能存取該資源;每個執行緒都會等候其前置項。 相反地,若未鎖定,則會依執行緒到達的順序來呼叫 UnSyncResource.Access 方法。