Monitor Monitor Monitor Monitor Class

定義

提供一套機制,同步處理物件的存取。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
繼承
MonitorMonitorMonitorMonitor
屬性

範例

下列範例會使用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. 此範例會建立 10 個工作,其中每一個執行緒集區執行緒以非同步方式執行。The example creates ten tasks, each of which executes asynchronously on a thread pool thread. 每個工作會產生 10,000 個隨機數字,計算其平均值,然後更新維護加總產生的隨機數字的數目和其總和的兩個程序層級變數。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)

從任何執行緒集區執行緒上執行的工作,可以存取它們,因為存取變數totaln也必須同步處理。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類別 (實作lockSyncLock語言建構),則Interlocked類別,而AutoResetEvent類別。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類別可讓您取得和釋放特定物件上的鎖定,藉由呼叫由同步處理的程式碼區域的存取Monitor.EnterMonitor.TryEnter,和Monitor.Exit方法。The 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:

監視類別:概觀 The Monitor class: An overview
鎖定物件 The lock object
重要區段 The critical section
Pulse 及 PulseAll 等候 Pulse, PulseAll, and Wait
監視與等候控制代碼Monitors and wait handles

監視類別:概觀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.

注意

使用Monitor字串以外的鎖定物件的類別 (也就是參考型別以外String),不實值型別。Use 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 信號用來通知等候中執行緒的相關物件的狀態變更。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.

開頭.NET Framework 4.NET Framework 4,有兩組多載EnterTryEnter方法。Beginning with the .NET Framework 4.NET Framework 4, there are two sets of overloads for the Enter and TryEnter methods. 有一組多載ref(在 C# 中) 或ByRef(在 Visual Basic)Boolean參數,自動設定為true如果已取得鎖定,即使取得鎖定時,會擲回例外狀況。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.

鎖定物件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,它會個別針對每個呼叫進行 boxed 處理。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. 它會啟動 10 個工作,其中每個工作睡眠 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.

每個工作都擲回 SynchronizationLockException 例外狀況是因為 nTasks 變數在呼叫每個工作中的 Monitor.Enter 方法之前會進行 boxed 處理。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 方法時會再次進行 boxed 處理。nTasks is boxed again in the call to the Monitor.Exit method. 同樣地,這會建立十個彼此獨立的新 boxed 變數 nTasks,以及在呼叫 Monitor.Enter 方法時建立的十個 boxed 變數。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.

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

請注意,您可以同步處理多個應用程式定義域中的物件如果用於鎖定的物件衍生自MarshalByRefObjectNote that you can synchronize on an object in multiple application domains if the object used for the lock derives from MarshalByRefObject.

重要區段The critical section

使用EnterExit標記開頭和結尾的重要區段的方法。Use the Enter and Exit methods to mark the beginning and end of a critical section.

注意

所提供的功能EnterExit方法等同於所提供的鎖定在 C# 中的陳述式和SyncLock以外,在 Visual Basic 中的陳述式語言建構包裝Monitor.Enter(Object, Boolean)方法多載而Monitor.Exit方法中的try...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.MethodImplAttributeIf 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. 當您使用這個屬性,EnterExit方法呼叫則不需要。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#鎖定陳述式或 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.

雖然您可以針對EnterExit鎖定並釋放跨成員或類別界限或兩者的指定的物件的陳述式,不建議這種做法。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.

Pulse 及 PulseAll 等候Pulse, PulseAll, and Wait

一旦執行緒擁有鎖定,而且已進入鎖定保護重要區段,它可以呼叫Monitor.WaitMonitor.Pulse,和Monitor.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. 執行緒會從等待佇列移到就緒佇列時任一Monitor.Pulse(要移動,執行緒必須等待佇列的開頭) 或Monitor.PulseAll持有鎖定的執行緒會呼叫方法。The thread is moved from the waiting queue to the ready queue when either the Monitor.Pulse (to be moved, the thread must be at the head of the waiting queue) or the Monitor.PulseAll method is called by the thread that holds the lock. 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類別是純粹 managed、 完全可攜性,而且可能是作業系統資源需求方面更有效率。The Monitor class is purely managed, fully portable, and might be more efficient in terms of operating-system resource requirements.

  • WaitHandle 物件代表作業系統可等候物件、適用於 managed 和 unmanaged 程式碼之間的同步處理,並且公開一些進階的作業系統功能,例如一次等候許多物件的能力。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.

方法

Enter(Object) Enter(Object) Enter(Object) Enter(Object)

取得指定物件的獨佔鎖定。Acquires an exclusive lock on the specified object.

Enter(Object, Boolean) Enter(Object, Boolean) Enter(Object, Boolean) 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) Exit(Object) Exit(Object) Exit(Object)

釋出指定物件的獨佔鎖定。Releases an exclusive lock on the specified object.

IsEntered(Object) IsEntered(Object) IsEntered(Object) IsEntered(Object)

判斷目前執行緒是否保持鎖定指定的物件。Determines whether the current thread holds the lock on the specified object.

Pulse(Object) Pulse(Object) Pulse(Object) Pulse(Object)

通知等候佇列中的執行緒,鎖定物件的狀態有所變更。Notifies a thread in the waiting queue of a change in the locked object's state.

PulseAll(Object) PulseAll(Object) PulseAll(Object) PulseAll(Object)

通知所有等候中的執行緒,物件的狀態有所變更。Notifies all waiting threads of a change in the object's state.

TryEnter(Object, TimeSpan, Boolean) TryEnter(Object, TimeSpan, Boolean) TryEnter(Object, TimeSpan, Boolean) 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.

TryEnter(Object, Int32, Boolean) TryEnter(Object, Int32, Boolean) TryEnter(Object, Int32, Boolean) 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) TryEnter(Object, TimeSpan) TryEnter(Object, TimeSpan) TryEnter(Object, TimeSpan)

嘗試取得指定物件的獨佔鎖定 (在指定的時間內)。Attempts, for the specified amount of time, to acquire an exclusive lock on the specified object.

TryEnter(Object, Int32) TryEnter(Object, Int32) TryEnter(Object, Int32) TryEnter(Object, Int32)

嘗試取得指定物件的獨佔鎖定 (在指定的毫秒數時間內)。Attempts, for the specified number of milliseconds, to acquire an exclusive lock on the specified object.

TryEnter(Object, Boolean) TryEnter(Object, Boolean) TryEnter(Object, Boolean) 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) TryEnter(Object) TryEnter(Object) TryEnter(Object)

嘗試取得指定物件的獨佔鎖定。Attempts to acquire an exclusive lock on the specified object.

Wait(Object) Wait(Object) Wait(Object) Wait(Object)

釋出物件的鎖並且封鎖目前的執行緒,直到這個執行緒重新取得鎖定為止。Releases the lock on an object and blocks the current thread until it reacquires the lock.

Wait(Object, Int32) Wait(Object, Int32) Wait(Object, Int32) 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, TimeSpan) Wait(Object, TimeSpan) Wait(Object, TimeSpan) 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, Int32, Boolean) Wait(Object, Int32, Boolean) Wait(Object, Int32, Boolean) 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. 這個方法也會指定等候之前和重新取得之後,是否要離開內容 (Context) 的同步處理領域 (如果在同步化內容中的話)。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, Boolean) Wait(Object, TimeSpan, Boolean) Wait(Object, TimeSpan, Boolean) 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.

另請參閱