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個のタスクを作成します。 各タスクは、1万の乱数を生成し、平均値を計算し、生成された乱数の合計数と合計を保持する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)

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

次の例で Monitor は、クラス ( lock または SyncLock 言語コンストラクトで実装)、クラス、およびクラス Interlocked 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 メソッドを呼び出します。 メソッドが呼び出されるたびに、アプリケーションスレッドは system.threading.waithandle.waitone メソッドを呼び出して、 AutoResetEvent インスタンスがシグナル状態になるまでブロックされるようにします。

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

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

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

注釈

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

この記事の内容:

Monitor クラス: 概要
Lock オブジェクト
クリティカルセクション
Pulse、System.threading.monitor.pulseall、Wait
モニターと待機ハンドル

Monitor クラス: 概要

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

  • 要求時にオブジェクトに関連付けられています。

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

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

注意

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

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

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

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

Lock オブジェクト

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

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

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

  • 待機中のキューへの参照。ロックされたオブジェクトの状態の変更の通知を待機しているスレッドを格納します。

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 ます。

クリティカルセクション

Enter Exit クリティカルセクションの開始と終了をマークするには、メソッドとメソッドを使用します。

注意

メソッドとメソッドによって提供さ Enter Exit れる機能は、C# のlockステートメントと Visual Basic のSyncLockステートメントによって提供される機能と同じです。ただし、言語構成要素では、 Monitor.Enter(Object, Boolean) メソッドのオーバーロードと Monitor.Exit メソッドを try ...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 メソッドに配置し、のコンストラクターで値を指定することによって、ロック機能を実現でき Synchronized System.Runtime.CompilerServices.MethodImplAttribute ます。 この属性を使用する場合、 Enter Exit メソッドとメソッドの呼び出しは必要ありません。 次のコードは、このパターンを示しています。

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

属性によって、メソッドが戻るまで、ロックを保持するために、現在のスレッドに注意してください。ロックがすぐに解放する場合は、使用、Monitorクラスの C#ロックステートメント、または Visual Basic SyncLock属性ではなく、メソッド内でステートメント。

Enter Exit 指定されたオブジェクトをロックして解放するステートメントとステートメントは、メンバーまたはクラスの境界の間またはその両方になる可能性がありますが、この方法はお勧めできません。

Pulse、System.threading.monitor.pulseall、Wait

スレッドがロックを所有していて、ロックによって保護されているクリティカルセクションに入ると、、、およびの各メソッドを呼び出すことができ Monitor.Wait Monitor.Pulse Monitor.PulseAll ます。

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

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

モニターと待機ハンドル

クラスとオブジェクトの使用方法の違いに注意することが重要です Monitor WaitHandle

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

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

適用対象

スレッド セーフ

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

こちらもご覧ください