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.TryEnterして、特定のオブジェクトに対するロックを取得および解放することで、コード領域へのアクセスをMonitor.Exit同期できます。 オブジェクト ロックを使用すると、一般的にクリティカル セクションと呼ばれるコード ブロックへのアクセスを制限できます。 スレッドはオブジェクトのロックを所有していますが、そのロックを取得できるスレッドは他にありません。 また、 クラスを Monitor 使用して、他のスレッドが別のロックされたオブジェクトを使用してコードを実行していない限り、ロック所有者によって実行されているアプリケーション コードのセクションへのアクセスを他のスレッドが許可されないようにすることもできます。 Monitor クラスにはスレッド アフィニティがあるため、ロックを取得したスレッドは Monitor.Exit メソッドを呼び出してロックを解放する必要があります。

この記事の内容:

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

Monitor クラス: 概要

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

  • これは、必要に応じて オブジェクトに関連付けられます。

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

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

Note

クラスを 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 クラスは、クリティカル セクションへのアクセスを 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派生している場合は、複数のアプリケーション ドメイン内のオブジェクトで同期できることに注意してください。

クリティカル セクション

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

Note

メソッドと Exit メソッドによってEnter提供される機能は、C# の lock ステートメントと Visual Basic の SyncLock ステートメントによって提供される機能と同じです。ただし、言語コンストラクトによって メソッドオーバーロードと メソッドtryMonitor.Exit ラップ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指定することで、ロック機能を実現できます。 この属性を使用する場合、 メソッドと Exit メソッドのEnter呼び出しは必要ありません。 次のコード フラグメントは、このパターンを示しています。

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

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

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

Pulse、PulseAll、Wait

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

ロックを保持しているスレッドが を呼び出 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)

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

適用対象

スレッド セーフ

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

こちらもご覧ください