Monitor クラス

定義

オブジェクトへのアクセスを同期する機構を提供します。

public ref class Monitor abstract sealed
public ref class Monitor sealed
public static class Monitor
public sealed class Monitor
[System.Runtime.InteropServices.ComVisible(true)]
public static class Monitor
type Monitor = class
[<System.Runtime.InteropServices.ComVisible(true)>]
type Monitor = class
Public Class Monitor
Public NotInheritable Class Monitor
継承
Monitor
属性

次の例では、クラスを Monitor 使用して、クラスによって表される乱数ジェネレーターの 1 つのインスタンスへのアクセスを Random 同期します。 この例では、10 個のタスクを作成します。各タスクはスレッド プール スレッドで非同期に実行されます。 各タスクは、10,000 個の乱数を生成し、その平均を計算し、生成された乱数とその合計の実行合計を維持する 2 つのプロシージャ レベル変数を更新します。 すべてのタスクが実行されると、これら 2 つの値を使用して全体の平均が計算されます。

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

public class Example
{
   public static void Main()
   {
      List<Task> tasks = new List<Task>();
      Random rnd = new Random();
      long total = 0;
      int n = 0;
      
      for (int taskCtr = 0; taskCtr < 10; taskCtr++)
         tasks.Add(Task.Run( () => {  int[] values = new int[10000];
                                      int taskTotal = 0;
                                      int taskN = 0;
                                      int ctr = 0;
                                      Monitor.Enter(rnd);
                                         // Generate 10,000 random integers
                                         for (ctr = 0; ctr < 10000; ctr++)
                                            values[ctr] = rnd.Next(0, 1001);
                                      Monitor.Exit(rnd);
                                      taskN = ctr;
                                      foreach (var value in values)
                                         taskTotal += value;

                                      Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
                                                        Task.CurrentId, (taskTotal * 1.0)/taskN,
                                                        taskN);
                                      Interlocked.Add(ref n, taskN);
                                      Interlocked.Add(ref total, taskTotal);
                                    } ));
      try {
         Task.WaitAll(tasks.ToArray());
         Console.WriteLine("\nMean for all tasks: {0:N2} (N={1:N0})",
                           (total * 1.0)/n, n);
      }
      catch (AggregateException e) {
         foreach (var ie in e.InnerExceptions)
            Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message);
      }
   }
}
// The example displays output like the following:
//       Mean for task  1: 499.04 (N=10,000)
//       Mean for task  2: 500.42 (N=10,000)
//       Mean for task  3: 499.65 (N=10,000)
//       Mean for task  8: 502.59 (N=10,000)
//       Mean for task  5: 502.75 (N=10,000)
//       Mean for task  4: 494.88 (N=10,000)
//       Mean for task  7: 499.22 (N=10,000)
//       Mean for task 10: 496.45 (N=10,000)
//       Mean for task  6: 499.75 (N=10,000)
//       Mean for task  9: 502.79 (N=10,000)
//
//       Mean for all tasks: 499.75 (N=100,000)
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim tasks As New List(Of Task)()
      Dim rnd As New Random()
      Dim total As Long = 0
      Dim n As Integer = 0

      For taskCtr As Integer = 0 To 9
         tasks.Add(Task.Run( Sub()
                                Dim values(9999) As Integer
                                Dim taskTotal As Integer = 0
                                Dim taskN As Integer = 0
                                Dim ctr As Integer = 0
                                Monitor.Enter(rnd)
                                   ' Generate 10,000 random integers.
                                    For ctr = 0 To 9999
                                       values(ctr) = rnd.Next(0, 1001)
                                    Next
                                Monitor.Exit(rnd)
                                taskN = ctr
                                For Each value in values
                                   taskTotal += value
                                Next
                                    
                                Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
                                                  Task.CurrentId, taskTotal/taskN,
                                                  taskN)
                                Interlocked.Add(n, taskN)
                                Interlocked.Add(total, taskTotal)
                             End Sub ))
      Next
      
      Try
         Task.WaitAll(tasks.ToArray())
         Console.WriteLine()
         Console.WriteLine("Mean for all tasks: {0:N2} (N={1:N0})",
                           (total * 1.0)/n, n)
      Catch e As AggregateException
         For Each ie In e.InnerExceptions
            Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message)
         Next
      End Try
   End Sub
End Module
' The example displays output like the following:
'       Mean for task  1: 499.04 (N=10,000)
'       Mean for task  2: 500.42 (N=10,000)
'       Mean for task  3: 499.65 (N=10,000)
'       Mean for task  8: 502.59 (N=10,000)
'       Mean for task  5: 502.75 (N=10,000)
'       Mean for task  4: 494.88 (N=10,000)
'       Mean for task  7: 499.22 (N=10,000)
'       Mean for task 10: 496.45 (N=10,000)
'       Mean for task  6: 499.75 (N=10,000)
'       Mean for task  9: 502.79 (N=10,000)
'
'       Mean for all tasks: 499.75 (N=100,000)

スレッド プール スレッドで実行されている任意のタスクからアクセスできるため、変数 total へのアクセスも n 同期する必要があります。 この Interlocked.Add 目的にはメソッドが使用されます。

次の例では、クラス (またはSyncLock言語コンストラクトでlock実装)、クラス、およびクラスをInterlocked組み合わせて使用Monitorする方法をAutoResetEvent示します。 2 つの 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 を定義します。 アプリケーション スレッドは、同期アクセスの場合も非同期アクセスの場合もそれぞれ 5 回、ThreadPool.QueueUserWorkItem(WaitCallback) メソッドを呼び出します。 ThreadPool.QueueUserWorkItem(WaitCallback) メソッドにはパラメーターが 1 つしかありません。パラメーターを受け入れず値を返さないデリゲートです。 同期アクセスの場合は SyncUpdateResource メソッドを呼び出し、非同期アクセスの場合は UnSyncUpdateResource メソッドを呼び出します。 メソッド呼び出しの各セットの後、アプリケーション スレッドは AutoResetEvent.WaitOne メソッドを呼び出し、インスタンスが通知されるまで AutoResetEvent ブロックします。

SyncUpdateResource メソッドを呼び出すたびに、内部 SyncResource.Access メソッドが呼び出され、Interlocked.Decrement メソッドが呼び出されて、numOps カウンターがデクリメントされます。 メソッドは Interlocked.Decrement カウンターをデクリメントするために使用されます。それ以外の場合は、最初のスレッドのデクリメントされた値が変数に格納される前に、2 番目のスレッドが値にアクセスすることを確信できないためです。 最後に同期されたワーカー スレッドがカウンターを 0 に減らし、すべての同期スレッドがリソースへのアクセスを完了したことを示す場合、 SyncUpdateResource メソッドはメソッドを呼び出し EventWaitHandle.Set 、メイン スレッドに実行を続行するように通知します。

UnSyncUpdateResource メソッドを呼び出すたびに、内部 UnSyncResource.Access メソッドが呼び出され、Interlocked.Decrement メソッドが呼び出されて、numOps カウンターがデクリメントされます。 もう一度、メソッドを使用してカウンターをデクリメントし、 Interlocked.Decrement 最初のスレッドのデクリメントされた値が変数に割り当てられる前に、2 番目のスレッドが値にアクセスしないようにします。 最後の非同期ワーカー スレッドがカウンターを 0 に減らし、非同期スレッドがリソースにアクセスする必要がないことを示す場合、 UnSyncUpdateResource メソッドはメソッドを呼び出し EventWaitHandle.Set 、メイン スレッドに実行を続行するように通知します。

例の出力からわかるように、同期アクセスでは、呼び出し元スレッドが保護リソースを終了してからでないと別のスレッドがそれにアクセスできません。つまり各スレッドはその先行処理を待機します。 その一方で、ロックがない UnSyncResource.Access メソッドは、スレッドが到達する順序で呼び出されます。

注釈

このMonitorクラスを使用すると、特定のオブジェクトMonitor.EnterMonitor.TryEnterMonitor.Exitに対するロックを取得して解放することで、コードの領域へのアクセスを同期できます。 オブジェクト ロックを使用すると、一般的にクリティカル セクションと呼ばれるコード ブロックへのアクセスを制限できます。 スレッドはオブジェクトのロックを所有していますが、他のスレッドはそのロックを取得できません。 このクラスを Monitor 使用して、ロックされた別のオブジェクトを使用して他のスレッドがコードを実行していない限り、ロック所有者によって実行されているアプリケーション コードのセクションに他のスレッドがアクセスできないようにすることもできます。

この記事の内容:

Monitor クラス: 概要
ロック オブジェクト
クリティカル セクション
Pulse、PulseAll、Wait
モニターと待機ハンドル

Monitor クラス: 概要

Monitor には次の機能があります。

  • これは、オンデマンドでオブジェクトに関連付けられます。

  • バインドされていないので、任意のコンテキストから直接呼び出すことができます。

  • クラスのインスタンスは Monitor 作成できません。クラスの Monitor メソッドはすべて静的です。 各メソッドには、クリティカル セクションへのアクセスを制御する同期オブジェクトが渡されます。

注意

このクラスを Monitor 使用して、値型ではなく、文字列以外のオブジェクト (つまり、以外の String参照型) をロックします。 詳細については、この記事の後半の「メソッドとロック オブジェクト」セクションのオーバーロードEnterを参照してください。

次の表では、同期されたオブジェクトにアクセスするスレッドが実行できるアクションについて説明します。

アクション 説明
Enter, TryEnter オブジェクトのロックを取得します。 このアクションは、クリティカル セクションの先頭にもマークを付けます。 別のロックされたオブジェクトを使用してクリティカル セクションの命令を実行していない限り、クリティカル セクションに入ることができるスレッドは他にありません。
Wait 他のスレッドがオブジェクトをロックおよびアクセスできるようにするために、オブジェクトのロックを解放します。 呼び出し元のスレッドは、別のスレッドがオブジェクトにアクセスするまで待機します。 パルス信号は、オブジェクトの状態の変化について待機中のスレッドに通知するために使用されます。
Pulse (シグナル) PulseAll 1 つ以上の待機中のスレッドにシグナルを送信します。 シグナルは、ロックされたオブジェクトの状態が変更され、ロックの所有者がロックを解除する準備ができていることを待機中のスレッドに通知します。 待機中のスレッドは、最終的にオブジェクトのロックを受け取ることができるように、オブジェクトの準備完了キューに配置されます。 スレッドにロックが設定されると、オブジェクトの新しい状態を確認して、必要な状態に達したかどうかを確認できます。
Exit オブジェクトのロックを解放します。 このアクションでは、ロックされたオブジェクトによって保護されたクリティカル セクションの末尾もマークされます。

.NET Framework 4 以降では、メソッドには TryEnter 2 つのオーバーロード Enter セットがあります。 1 セットのオーバーロードにはref、ロックの取得時に例外がスローされた場合でも、ロックが取得された場合にアトミックにtrue設定される (C#の) または ByRef (Visual Basic) Boolean パラメーターがあります。 ロックが保護されているリソースが一貫した状態ではない可能性がある場合でも、すべてのケースでロックを解放することが重要な場合は、これらのオーバーロードを使用します。

ロック オブジェクト

Monitor クラスは、クリティカル セクションへのアクセスを制御するオブジェクトを操作する (C# の場合) または Shared (Visual Basic) メソッドで構成されますstatic。 同期されたオブジェクトごとに、次の情報が保持されます。

  • 現在ロックを保持しているスレッドへの参照。

  • ロックを取得する準備ができているスレッドを含む準備完了キューへの参照。

  • ロックされたオブジェクトの状態の変化の通知を待機しているスレッドを含む待機キューへの参照。

Monitor は値型ではなく、オブジェクト (つまり、参照型) をロックします。 値型を EnterExit に渡すことができますが、値型は呼び出しごとに個別にボックス化されます。 呼び出しごとに個別のオブジェクトが作成されるので、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 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.

各タスクの Monitor.Enter メソッドに対する呼び出しの前に nTasks 変数がボックス化されるため、各タスクは SynchronizationLockException 例外をスローします。 つまり、各メソッドの呼び出しは他のメソッドから独立している個別の変数に渡されます。 nTasksMonitor.Exit メソッドへの呼び出しで再びボックス化されます。 こうして 10 個の新しいボックス化された変数が作成されます。これらは互いに独立したものであり、nTasks からも Monitor.Enter メソッドへの呼び出しで作成された 10 個のボックス化された変数からも独立しています。 それで、以前ロックされていなかった新規に作成された変数のロックを解放しようとしているため、例外がスローされます。

次の例に示すように、EnterExit の呼び出しの前に値型の変数をボックス化したり、ボックス化された同じオブジェクトを両方のメソッドに渡したりできますが、これを行う利点はありません。 ボックス化解除された変数への変更は、ボックス化されたコピーには反映されません。またボックス化されたコピーの値を変更する方法はありません。

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

public class Example
{
   public static void Main()
   {

      int nTasks = 0;
      object o = nTasks;
      List<Task> tasks = new List<Task>();

      try {
         for (int ctr = 0; ctr < 10; ctr++)
            tasks.Add(Task.Run( () => { // Instead of doing some work, just sleep.
                                        Thread.Sleep(250);
                                        // Increment the number of tasks.
                                        Monitor.Enter(o);
                                        try {
                                           nTasks++;
                                        }
                                        finally {
                                           Monitor.Exit(o);
                                        }
                                      } ));
         Task.WaitAll(tasks.ToArray());
         Console.WriteLine("{0} tasks started and executed.", nTasks);
      }
      catch (AggregateException e) {
         String msg = String.Empty;
         foreach (var ie in e.InnerExceptions) {
            Console.WriteLine("{0}", ie.GetType().Name);
            if (! msg.Contains(ie.Message))
               msg += ie.Message + Environment.NewLine;
         }
         Console.WriteLine("\nException Message(s):");
         Console.WriteLine(msg);
      }
   }
}
// The example displays the following output:
//        10 tasks started and executed.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Module Example
   Public Sub Main()
      Dim nTasks As Integer = 0
      Dim o As Object = nTasks
      Dim tasks As New List(Of Task)()

      Try
         For ctr As Integer = 0 To 9
            tasks.Add(Task.Run( Sub()
                                   ' Instead of doing some work, just sleep.
                                   Thread.Sleep(250)
                                   ' Increment the number of tasks.
                                   Monitor.Enter(o)
                                   Try
                                      nTasks += 1
                                   Finally
                                      Monitor.Exit(o)
                                   End Try
                                End Sub))
         Next
         Task.WaitAll(tasks.ToArray())
         Console.WriteLine("{0} tasks started and executed.", nTasks)
      Catch e As AggregateException
         Dim msg AS String = String.Empty
         For Each ie In e.InnerExceptions
            Console.WriteLine("{0}", ie.GetType().Name)
            If Not msg.Contains(ie.Message) Then
               msg += ie.Message + Environment.NewLine
            End If
         Next
         Console.WriteLine(vbCrLf + "Exception Message(s):")
         Console.WriteLine(msg)
      End Try
   End Sub
End Module
' The example displays the following output:
'       10 tasks started and executed.

同期するオブジェクトを選択するときは、プライベート オブジェクトまたは内部オブジェクトでのみロックする必要があります。 外部オブジェクトをロックするとデッドロックが発生する可能性があります。これは、関連のないコードが、異なる目的でロックする同じオブジェクトを選択する可能性があるためです。

ロックに使用されるオブジェクトの派生元 MarshalByRefObjectである場合は、複数のアプリケーション ドメイン内のオブジェクトで同期できることに注意してください。

クリティカル セクション

メソッドをEnterExit使用して、クリティカル セクションの先頭と末尾をマークします。

注意

メソッドによって提供されるEnter機能は、C# の lock ステートメントと Visual Basic の SyncLock ステートメントによって提供される機能とExit同じですが、言語コンストラクトがメソッド オーバーロードとメソッドを Monitor.Exit ... でtryラップMonitor.Enter(Object, Boolean)する点が異なります。finally をブロックして、モニターが確実に解放されるようにします。

クリティカル セクションが連続した命令のセットである場合、メソッドによって取得されたロックにより Enter 、ロックされたオブジェクトで囲まれたコードを実行できるのは 1 つのスレッドだけであることが保証されます。 この場合は、そのコードをブロックに 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#ロックステートメント、または Visual Basic SyncLock属性ではなく、メソッド内でステートメント。

特定のオブジェクトを Enter ロックおよび解放する and Exit ステートメントは、メンバーまたはクラスの境界またはその両方を超える場合は可能ですが、この方法はお勧めしません。

Pulse、PulseAll、Wait

スレッドがロックを所有し、ロックが保護する重要なセクションに入ると、そのスレッドは 、、Monitor.PulseおよびMonitor.PulseAllメソッドをMonitor.Wait呼び出すことができます。

ロックを保持しているスレッドが呼び出 Waitされると、ロックが解放され、スレッドが同期オブジェクトの待機キューに追加されます。 準備完了キュー内の最初のスレッド (存在する場合) はロックを取得し、クリティカル セクションに入ります。 呼び出されたWaitスレッドは、ロックを保持するスレッドによってメソッドがMonitor.PulseAll呼び出されたときにMonitor.Pulse、待機キューから準備完了キューに移動されます (移動するには、スレッドが待機キューの先頭にある必要があります)。 呼 Wait び出し元のスレッドがロックを再取得すると、メソッドが返します。

ロックを保持するスレッドが呼び出 Pulseされると、待機キューの先頭にあるスレッドが準備完了キューに移動されます。 メソッドを呼び出すと、 PulseAll すべてのスレッドが待機キューから準備完了キューに移動されます。

モニターと待機ハンドル

クラスとWaitHandleオブジェクトの使用Monitorの区別に注意することが重要です。

  • このクラスは Monitor 純粋に管理され、完全に移植可能であり、オペレーティング システムのリソース要件の観点からより効率的な場合があります。

  • WaitHandle オブジェクトはオペレーティング システムの待機可能オブジェクトを表しており、マネージドとアンマネージド コード間で同期するのに便利です。また一度に多くのオブジェクトを待機できる機能などの高度なオペレーティング システム機能を公開します。

プロパティ

LockContentionCount

モニターのロックを取得しようとするときに、接続があった回数を取得します。

メソッド

Enter(Object)

指定したオブジェクトの排他ロックを取得します。

Enter(Object, Boolean)

指定したオブジェクトの排他ロックを取得し、ロックが取得されたかどうかを示す値をアトミックに設定します。

Exit(Object)

指定したオブジェクトの排他ロックを解放します。

IsEntered(Object)

現在のスレッドが指定したオブジェクトのロックを保持しているかどうかを判断します。

Pulse(Object)

ロックされたオブジェクトの状態が変更されたことを、待機キュー内のスレッドに通知します。

PulseAll(Object)

オブジェクトの状態が変更されたことを、待機中のすべてのスレッドに通知します。

TryEnter(Object)

指定したオブジェクトの排他ロックの取得を試みます。

TryEnter(Object, Boolean)

指定したオブジェクトの排他ロックの取得を試み、ロックが取得されたかどうかを示す値をアトミックに設定します。

TryEnter(Object, Int32)

指定したミリ秒間に、指定したオブジェクトの排他ロックの取得を試みます。

TryEnter(Object, Int32, Boolean)

指定したオブジェクトの排他ロックの取得を指定したミリ秒間試み、ロックが取得されたかどうかを示す値をアトミックに設定します。

TryEnter(Object, TimeSpan)

指定した時間内に、指定したオブジェクトの排他ロックの取得を試みます。

TryEnter(Object, TimeSpan, Boolean)

指定したオブジェクトの排他ロックの取得を指定した時間にわたって試み、ロックが取得されたかどうかを示す値をアトミックに設定します。

Wait(Object)

オブジェクトのロックを解放し、現在のスレッドがロックを再取得するまでそのスレッドをブロックします。

Wait(Object, Int32)

オブジェクトのロックを解放し、現在のスレッドがロックを再取得するまでそのスレッドをブロックします。 指定されたタイムアウト期限を過ぎると、スレッドは実行待ちキューに入ります。

Wait(Object, Int32, Boolean)

オブジェクトのロックを解放し、現在のスレッドがロックを再取得するまでそのスレッドをブロックします。 指定されたタイムアウト期限を過ぎると、スレッドは実行待ちキューに入ります。 このメソッドは、コンテキストの同期ドメイン (同期されたコンテキストの場合) が待機の前に終了し、後で再取得されるかどうかも指定します。

Wait(Object, TimeSpan)

オブジェクトのロックを解放し、現在のスレッドがロックを再取得するまでそのスレッドをブロックします。 指定されたタイムアウト期限を過ぎると、スレッドは実行待ちキューに入ります。

Wait(Object, TimeSpan, Boolean)

オブジェクトのロックを解放し、現在のスレッドがロックを再取得するまでそのスレッドをブロックします。 指定されたタイムアウト期限を過ぎると、スレッドは実行待ちキューに入ります。 または、待機の前に同期化されたコンテキストの同期ドメインを終了し、ドメインを後で再取得します。

適用対象

スレッド セーフ

この型はスレッド セーフです。

こちらもご覧ください