この記事では、この API のリファレンス ドキュメントに補足的な解説を提供します。
Monitor クラスを使用すると、Monitor.Enter、Monitor.TryEnter、およびMonitor.Exitメソッドを呼び出して、特定のオブジェクトに対するロックを取得および解放することで、コード領域へのアクセスを同期できます。 オブジェクト ロックを使用すると、コード ブロック (一般にクリティカル セクションと呼ばれます) へのアクセスを制限できます。 スレッドはオブジェクトのロックを所有していますが、他のスレッドはそのロックを取得できません。 また、 Monitor クラスを使用して、他のスレッドが別のロックされたオブジェクトを使用してコードを実行していない限り、ロック所有者によって実行されているアプリケーション コードのセクションに他のスレッドがアクセスできないようにすることもできます。 Monitor クラスにはスレッド アフィニティがあるため、ロックを取得したスレッドは、Monitor.Exit メソッドを呼び出してロックを解放する必要があります。
概要
Monitor には、次の機能があります。
- これは、必要に応じてオブジェクトに関連付けられます。
- バインドされていないので、任意のコンテキストから直接呼び出すことができます。
- Monitor クラスのインスタンスを作成できません。Monitor クラスのメソッドはすべて静的です。 各メソッドには、クリティカル セクションへのアクセスを制御する同期オブジェクトが渡されます。
注
Monitor クラスを使用して、値型ではなく、文字列以外のオブジェクト (つまり、String以外の参照型) をロックします。 詳細については、 Enter メソッドのオーバーロードと、この記事の後半の 「ロック オブジェクト 」セクションを参照してください。
次の表では、同期されたオブジェクトにアクセスするスレッドが実行できるアクションについて説明します。
アクション | 説明 |
---|---|
Enter、TryEnter | オブジェクトのロックを取得します。 このアクションは、クリティカル セクションの先頭にもマークを付けます。 別のロックされたオブジェクトを使用してクリティカル セクションの命令を実行している場合を除き、他のスレッドはクリティカル セクションに入ることができます。 |
Wait | 他のスレッドがオブジェクトをロックおよびアクセスできるようにするために、オブジェクトのロックを解放します。 呼び出し元のスレッドは、別のスレッドがオブジェクトにアクセスするまで待機します。 パルス信号は、オブジェクトの状態への変更について待機中のスレッドに通知するために使用されます。 |
Pulse (シグナル) PulseAll | 1 つ以上の待機中のスレッドにシグナルを送信します。 このシグナルは、ロックされたオブジェクトの状態が変更され、ロックの所有者がロックを解除する準備ができていることを待機中のスレッドに通知します。 待機中のスレッドは、最終的にオブジェクトのロックを受け取ることができるように、オブジェクトの準備完了キューに配置されます。 スレッドがロックされると、オブジェクトの新しい状態を確認して、必要な状態に達したかどうかを確認できます。 |
Exit | オブジェクトのロックを解除します。 このアクションは、ロックされたオブジェクトによって保護されているクリティカル セクションの末尾もマークします。 |
EnterメソッドとTryEnter メソッドには、2 つのオーバーロード セットがあります。 1 つのオーバーロードのセットには、ロックの取得時に例外がスローされた場合でも、ロックが取得された場合にアトミックにtrue
に設定されるref
(C# の場合) またはByRef
(Visual Basic の場合) Boolean パラメーターがあります。 ロックが保護されているリソースが一貫した状態ではない可能性がある場合でも、すべてのケースでロックを解放することが重要な場合は、これらのオーバーロードを使用します。
ロック オブジェクト
Monitor クラスは、クリティカル セクションへのアクセスを制御するオブジェクトを操作する static
(Visual Basic ではShared
) メソッドで構成されます。 同期されたオブジェクトごとに、次の情報が保持されます。
- 現在ロックを保持しているスレッドへの参照。
- ロックを取得する準備ができているスレッドを含む準備完了キューへの参照。
- 待機中のキューへの参照。ロックされたオブジェクトの状態の変化の通知を待機しているスレッドが含まれます。
Monitor は、値型ではなくオブジェクト (つまり、参照型) をロックします。 EnterとExitに値型を渡すことができますが、呼び出しごとに個別にボックス化されます。 各呼び出しでは個別のオブジェクトが作成されるため、 Enter はブロックされず、保護するコードは実際には同期されません。 さらに、 Exit に渡されるオブジェクトは、 Enterに渡されるオブジェクトとは異なるので、 Monitor は"同期されていないコード ブロックからオブジェクト同期メソッドが呼び出されました" というメッセージで SynchronizationLockException 例外をスローします。
次の例は、この問題を示しています。 10 個のタスクが起動され、それぞれが 250 ミリ秒間スリープ状態になります。 その後、各タスクはカウンター変数 nTasks
を更新します。これは、実際に起動および実行されたタスクの数をカウントするためのものです。
nTasks
は複数のタスクで同時に更新できるグローバル変数であるため、モニターは複数のタスクによる同時変更から保護するために使用されます。 ただし、この例の出力に示すように、各タスクは SynchronizationLockException 例外をスローします。
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example1
{
public static void Main()
{
int nTasks = 0;
List<Task> tasks = new List<Task>();
try
{
for (int ctr = 0; ctr < 10; ctr++)
tasks.Add(Task.Run(() =>
{ // Instead of doing some work, just sleep.
Thread.Sleep(250);
// Increment the number of tasks.
Monitor.Enter(nTasks);
try
{
nTasks += 1;
}
finally
{
Monitor.Exit(nTasks);
}
}));
Task.WaitAll(tasks.ToArray());
Console.WriteLine($"{nTasks} tasks started and executed.");
}
catch (AggregateException e)
{
String msg = String.Empty;
foreach (var ie in e.InnerExceptions)
{
Console.WriteLine($"{ie.GetType().Name}");
if (!msg.Contains(ie.Message))
msg += ie.Message + Environment.NewLine;
}
Console.WriteLine("\nException Message(s):");
Console.WriteLine(msg);
}
}
}
// The example displays the following output:
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
//
// Exception Message(s):
// Object synchronization method was called from an unsynchronized block of code.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example3
Public Sub Main()
Dim nTasks As Integer = 0
Dim tasks As New List(Of Task)()
Try
For ctr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
' Instead of doing some work, just sleep.
Thread.Sleep(250)
' Increment the number of tasks.
Monitor.Enter(nTasks)
Try
nTasks += 1
Finally
Monitor.Exit(nTasks)
End Try
End Sub))
Next
Task.WaitAll(tasks.ToArray())
Console.WriteLine("{0} tasks started and executed.", nTasks)
Catch e As AggregateException
Dim msg As String = String.Empty
For Each ie In e.InnerExceptions
Console.WriteLine("{0}", ie.GetType().Name)
If Not msg.Contains(ie.Message) Then
msg += ie.Message + Environment.NewLine
End If
Next
Console.WriteLine(vbCrLf + "Exception Message(s):")
Console.WriteLine(msg)
End Try
End Sub
End Module
' The example displays the following output:
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
'
' Exception Message(s):
' Object synchronization method was called from an unsynchronized block of code.
各タスクでMonitor.Enter メソッドを呼び出す前にnTasks
変数がボックス化されているため、各タスクはSynchronizationLockException例外をスローします。 つまり、各メソッド呼び出しには、他のメソッドから独立した個別の変数が渡されます。
nTasks
は、 Monitor.Exit メソッドの呼び出しで再びボックス化されます。 ここでも、Monitor.Enter メソッドの呼び出しで作成された 10 個のボックス化された変数が、互いに独立した 10 個の新しいボックス化された変数、nTasks
、および 10 個のボックス化された変数を作成します。 例外がスローされます。これは、以前にロックされていない新しく作成された変数に対して、コードがロックを解除しようとしているためです。
次の例に示すように、 Enter と Exitを呼び出す前に値型変数をボックス化し、同じボックス化されたオブジェクトを両方のメソッドに渡すことはできますが、これを行う利点はありません。 ボックス化されていない変数への変更はボックス化されたコピーには反映されず、ボックス化されたコピーの値を変更する方法はありません。
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($"{nTasks} tasks started and executed.");
}
catch (AggregateException e) {
String msg = String.Empty;
foreach (var ie in e.InnerExceptions) {
Console.WriteLine($"{ie.GetType().Name}");
if (! msg.Contains(ie.Message))
msg += ie.Message + Environment.NewLine;
}
Console.WriteLine("\nException Message(s):");
Console.WriteLine(msg);
}
}
}
// The example displays the following output:
// 10 tasks started and executed.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example2
Public Sub Main()
Dim nTasks As Integer = 0
Dim o As Object = nTasks
Dim tasks As New List(Of Task)()
Try
For ctr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
' Instead of doing some work, just sleep.
Thread.Sleep(250)
' Increment the number of tasks.
Monitor.Enter(o)
Try
nTasks += 1
Finally
Monitor.Exit(o)
End Try
End Sub))
Next
Task.WaitAll(tasks.ToArray())
Console.WriteLine("{0} tasks started and executed.", nTasks)
Catch e As AggregateException
Dim msg As String = String.Empty
For Each ie In e.InnerExceptions
Console.WriteLine("{0}", ie.GetType().Name)
If Not msg.Contains(ie.Message) Then
msg += ie.Message + Environment.NewLine
End If
Next
Console.WriteLine(vbCrLf + "Exception Message(s):")
Console.WriteLine(msg)
End Try
End Sub
End Module
' The example displays the following output:
' 10 tasks started and executed.
同期するオブジェクトを選択するときは、プライベート オブジェクトまたは内部オブジェクトでのみロックする必要があります。 外部オブジェクトをロックするとデッドロックが発生する可能性があります。これは、関連のないコードが、異なる目的でロックする同じオブジェクトを選択する可能性があるためです。
ロックに使用されるオブジェクトが MarshalByRefObjectから派生している場合は、複数のアプリケーション ドメイン内のオブジェクトで同期できることに注意してください。
クリティカル セクション
EnterメソッドとExitメソッドを使用して、クリティカル セクションの先頭と末尾をマークします。
注
EnterメソッドとExit メソッドによって提供される機能は、C# の lock ステートメントと Visual Basic の SyncLock ステートメントで提供される機能と同じですが、言語コンストラクトがMonitor.Enter(Object, Boolean) メソッドのオーバーロードとMonitor.Exit メソッドをtry
でラップする点が異なります。
finally
をブロックして、モニターが確実に解放されるようにします。
クリティカル セクションが連続した命令のセットである場合、 Enter メソッドによって取得されたロックにより、ロックされたオブジェクトで囲まれたコードを実行できるのは 1 つのスレッドのみであることが保証されます。 この場合は、そのコードを try
ブロックに配置し、finally
ブロック内の Exit メソッドの呼び出しを配置することをお勧めします。 これにより、例外が発生した場合でもロックが解放されます。 次のコード フラグメントは、このパターンを示しています。
// 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 ステートメントと Exit ステートメントがメンバーまたはクラスの境界を越えたり、その両方を解除したりすることは可能ですが、この方法はお勧めしません。
Pulse、PulseAll、Wait
スレッドがロックを所有し、ロックが保護する重要なセクションに入ると、 Monitor.Wait、 Monitor.Pulse、および Monitor.PulseAll メソッドを呼び出すことができます。
ロックを保持するスレッドが Waitを呼び出すと、ロックが解放され、同期されたオブジェクトの待機キューにスレッドが追加されます。 準備完了キュー内の最初のスレッド (存在する場合) はロックを取得し、クリティカル セクションに入ります。 Waitを呼び出したスレッドは、ロックを保持するスレッドによってMonitor.PulseまたはMonitor.PulseAll メソッドが呼び出されたときに、待機キューから準備完了キューに移動されます (移動するには、スレッドが待機キューの先頭にある必要があります)。 Wait メソッドは、呼び出し元のスレッドがロックを再取得するときに返します。
ロックを保持するスレッドが Pulse呼び出されると、待機キューの先頭にあるスレッドが準備完了キューに移動されます。 PulseAll メソッドを呼び出すと、すべてのスレッドが待機キューから準備完了キューに移動されます。
モニターと待機ハンドル
Monitor クラスとWaitHandle オブジェクトの使用の区別に注意することが重要です。
- Monitor クラスは純粋に管理され、完全に移植可能であり、オペレーティング システムのリソース要件の観点からより効率的な場合があります。
- WaitHandle オブジェクトはオペレーティング システムの待機可能なオブジェクトを表し、マネージド コードとアンマネージド コード間の同期に役立ち、多数のオブジェクトを一度に待機する機能など、いくつかの高度なオペレーティング システム機能を公開します。
例示
次の例では、 Monitor クラスを使用して、 Random クラスによって表される乱数ジェネレーターの単一インスタンスへのアクセスを同期します。 この例では、10 個のタスクを作成します。各タスクはスレッド プール スレッドで非同期的に実行されます。 各タスクは、10,000 個の乱数を生成し、その平均値を計算し、生成された乱数の数とその合計の実行合計を維持する 2 つのプロシージャ レベル変数を更新します。 すべてのタスクが実行された後、これら 2 つの値を使用して全体の平均が計算されます。
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example2
{
public static void Main()
{
List<Task> tasks = new List<Task>();
Random rnd = new Random();
long total = 0;
int n = 0;
for (int taskCtr = 0; taskCtr < 10; taskCtr++)
tasks.Add(Task.Run(() =>
{
int[] values = new int[10000];
int taskTotal = 0;
int taskN = 0;
int ctr = 0;
Monitor.Enter(rnd);
// Generate 10,000 random integers
for (ctr = 0; ctr < 10000; ctr++)
values[ctr] = rnd.Next(0, 1001);
Monitor.Exit(rnd);
taskN = ctr;
foreach (var value in values)
taskTotal += value;
Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
Task.CurrentId, (taskTotal * 1.0) / taskN,
taskN);
Interlocked.Add(ref n, taskN);
Interlocked.Add(ref total, taskTotal);
}));
try
{
Task.WaitAll(tasks.ToArray());
Console.WriteLine($"\nMean for all tasks: {(total * 1.0) / n:N2} (N={n:N0})");
}
catch (AggregateException e)
{
foreach (var ie in e.InnerExceptions)
Console.WriteLine($"{ie.GetType().Name}: {ie.Message}");
}
}
}
// The example displays output like the following:
// Mean for task 1: 499.04 (N=10,000)
// Mean for task 2: 500.42 (N=10,000)
// Mean for task 3: 499.65 (N=10,000)
// Mean for task 8: 502.59 (N=10,000)
// Mean for task 5: 502.75 (N=10,000)
// Mean for task 4: 494.88 (N=10,000)
// Mean for task 7: 499.22 (N=10,000)
// Mean for task 10: 496.45 (N=10,000)
// Mean for task 6: 499.75 (N=10,000)
// Mean for task 9: 502.79 (N=10,000)
//
// Mean for all tasks: 499.75 (N=100,000)
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example4
Public Sub Main()
Dim tasks As New List(Of Task)()
Dim rnd As New Random()
Dim total As Long = 0
Dim n As Integer = 0
For taskCtr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
Dim values(9999) As Integer
Dim taskTotal As Integer = 0
Dim taskN As Integer = 0
Dim ctr As Integer = 0
Monitor.Enter(rnd)
' Generate 10,000 random integers.
For ctr = 0 To 9999
values(ctr) = rnd.Next(0, 1001)
Next
Monitor.Exit(rnd)
taskN = ctr
For Each value In values
taskTotal += value
Next
Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
Task.CurrentId, taskTotal / taskN,
taskN)
Interlocked.Add(n, taskN)
Interlocked.Add(total, taskTotal)
End Sub))
Next
Try
Task.WaitAll(tasks.ToArray())
Console.WriteLine()
Console.WriteLine("Mean for all tasks: {0:N2} (N={1:N0})",
(total * 1.0) / n, n)
Catch e As AggregateException
For Each ie In e.InnerExceptions
Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message)
Next
End Try
End Sub
End Module
' The example displays output like the following:
' Mean for task 1: 499.04 (N=10,000)
' Mean for task 2: 500.42 (N=10,000)
' Mean for task 3: 499.65 (N=10,000)
' Mean for task 8: 502.59 (N=10,000)
' Mean for task 5: 502.75 (N=10,000)
' Mean for task 4: 494.88 (N=10,000)
' Mean for task 7: 499.22 (N=10,000)
' Mean for task 10: 496.45 (N=10,000)
' Mean for task 6: 499.75 (N=10,000)
' Mean for task 9: 502.79 (N=10,000)
'
' Mean for all tasks: 499.75 (N=100,000)
スレッド プール スレッドで実行されている任意のタスクからアクセスできるため、変数 total
と n
へのアクセスも同期する必要があります。 この目的には、 Interlocked.Add メソッドが使用されます。
次の例では、 Monitor クラス ( lock
または SyncLock
言語コンストラクトで実装)、 Interlocked クラス、および AutoResetEvent クラスを組み合わせて使用する方法を示します。
SyncResource
とUnSyncResource
の 2 つのinternal
(C#) クラスまたは Friend
(Visual Basic の場合) クラスを定義します。このクラスは、それぞれリソースへの同期されたアクセスと同期されていないアクセスを提供します。 この例では、同期されたアクセスと同期されていないアクセスの違い (各メソッド呼び出しが迅速に完了した場合など) を示すために、メソッドにはランダムな遅延が含まれます。 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 #{Thread.CurrentThread.ManagedThreadId}");
if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
Thread.Sleep(2000);
Thread.Sleep(200);
Console.WriteLine($"Stopping synchronized resource access on thread #{Thread.CurrentThread.ManagedThreadId}");
}
}
}
internal class UnSyncResource
{
// Do not enforce synchronization.
public void Access()
{
Console.WriteLine($"Starting unsynchronized resource access on Thread #{Thread.CurrentThread.ManagedThreadId}");
if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
Thread.Sleep(2000);
Thread.Sleep(200);
Console.WriteLine($"Stopping unsynchronized resource access on thread #{Thread.CurrentThread.ManagedThreadId}");
}
}
public class App
{
private static int numOps;
private static AutoResetEvent opsAreDone = new AutoResetEvent(false);
private static SyncResource SyncRes = new SyncResource();
private static UnSyncResource UnSyncRes = new UnSyncResource();
public static void Main()
{
// Set the number of synchronized calls.
numOps = 5;
for (int ctr = 0; ctr <= 4; ctr++)
ThreadPool.QueueUserWorkItem(new WaitCallback(SyncUpdateResource));
// Wait until this WaitHandle is signaled.
opsAreDone.WaitOne();
Console.WriteLine("\t\nAll synchronized operations have completed.\n");
// Reset the count for unsynchronized calls.
numOps = 5;
for (int ctr = 0; ctr <= 4; ctr++)
ThreadPool.QueueUserWorkItem(new WaitCallback(UnSyncUpdateResource));
// Wait until this WaitHandle is signaled.
opsAreDone.WaitOne();
Console.WriteLine("\t\nAll unsynchronized thread operations have completed.\n");
}
static void SyncUpdateResource(Object state)
{
// Call the internal synchronized method.
SyncRes.Access();
// Ensure that only one thread can decrement the counter at a time.
if (Interlocked.Decrement(ref numOps) == 0)
// Announce to Main that in fact all thread calls are done.
opsAreDone.Set();
}
static void UnSyncUpdateResource(Object state)
{
// Call the unsynchronized method.
UnSyncRes.Access();
// Ensure that only one thread can decrement the counter at a time.
if (Interlocked.Decrement(ref numOps) == 0)
// Announce to Main that in fact all thread calls are done.
opsAreDone.Set();
}
}
// The example displays output like the following:
// Starting synchronized resource access on thread #6
// Stopping synchronized resource access on thread #6
// Starting synchronized resource access on thread #7
// Stopping synchronized resource access on thread #7
// Starting synchronized resource access on thread #3
// Stopping synchronized resource access on thread #3
// Starting synchronized resource access on thread #4
// Stopping synchronized resource access on thread #4
// Starting synchronized resource access on thread #5
// Stopping synchronized resource access on thread #5
//
// All synchronized operations have completed.
//
// Starting unsynchronized resource access on Thread #7
// Starting unsynchronized resource access on Thread #9
// Starting unsynchronized resource access on Thread #10
// Starting unsynchronized resource access on Thread #6
// Starting unsynchronized resource access on Thread #3
// Stopping unsynchronized resource access on thread #7
// Stopping unsynchronized resource access on thread #9
// Stopping unsynchronized resource access on thread #3
// Stopping unsynchronized resource access on thread #10
// Stopping unsynchronized resource access on thread #6
//
// All unsynchronized thread operations have completed.
Imports System.Threading
Friend Class SyncResource
' Use a monitor to enforce synchronization.
Public Sub Access()
SyncLock Me
Console.WriteLine("Starting synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
Thread.Sleep(2000)
End If
Thread.Sleep(200)
Console.WriteLine("Stopping synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
End SyncLock
End Sub
End Class
Friend Class UnSyncResource
' Do not enforce synchronization.
Public Sub Access()
Console.WriteLine("Starting unsynchronized resource access on Thread #{0}",
Thread.CurrentThread.ManagedThreadId)
If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
Thread.Sleep(2000)
End If
Thread.Sleep(200)
Console.WriteLine("Stopping unsynchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
End Sub
End Class
Public Module App
Private numOps As Integer
Private opsAreDone As New AutoResetEvent(False)
Private SyncRes As New SyncResource()
Private UnSyncRes As New UnSyncResource()
Public Sub Main()
' Set the number of synchronized calls.
numOps = 5
For ctr As Integer = 0 To 4
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf SyncUpdateResource))
Next
' Wait until this WaitHandle is signaled.
opsAreDone.WaitOne()
Console.WriteLine(vbTab + Environment.NewLine + "All synchronized operations have completed.")
Console.WriteLine()
numOps = 5
' Reset the count for unsynchronized calls.
For ctr As Integer = 0 To 4
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf UnSyncUpdateResource))
Next
' Wait until this WaitHandle is signaled.
opsAreDone.WaitOne()
Console.WriteLine(vbTab + Environment.NewLine + "All unsynchronized thread operations have completed.")
End Sub
Sub SyncUpdateResource()
' Call the internal synchronized method.
SyncRes.Access()
' Ensure that only one thread can decrement the counter at a time.
If Interlocked.Decrement(numOps) = 0 Then
' Announce to Main that in fact all thread calls are done.
opsAreDone.Set()
End If
End Sub
Sub UnSyncUpdateResource()
' Call the unsynchronized method.
UnSyncRes.Access()
' Ensure that only one thread can decrement the counter at a time.
If Interlocked.Decrement(numOps) = 0 Then
' Announce to Main that in fact all thread calls are done.
opsAreDone.Set()
End If
End Sub
End Module
' The example displays output like the following:
' Starting synchronized resource access on thread #6
' Stopping synchronized resource access on thread #6
' Starting synchronized resource access on thread #7
' Stopping synchronized resource access on thread #7
' Starting synchronized resource access on thread #3
' Stopping synchronized resource access on thread #3
' Starting synchronized resource access on thread #4
' Stopping synchronized resource access on thread #4
' Starting synchronized resource access on thread #5
' Stopping synchronized resource access on thread #5
'
' All synchronized operations have completed.
'
' Starting unsynchronized resource access on Thread #7
' Starting unsynchronized resource access on Thread #9
' Starting unsynchronized resource access on Thread #10
' Starting unsynchronized resource access on Thread #6
' Starting unsynchronized resource access on Thread #3
' Stopping unsynchronized resource access on thread #7
' Stopping unsynchronized resource access on thread #9
' Stopping unsynchronized resource access on thread #3
' Stopping unsynchronized resource access on thread #10
' Stopping unsynchronized resource access on thread #6
'
' All unsynchronized thread operations have completed.
この例では、リソースへのアクセスを試みるスレッドの数を定義する変数 ( numOps
) を定義します。 アプリケーション スレッドは、同期および同期されていないアクセスに対して ThreadPool.QueueUserWorkItem(WaitCallback) メソッドを 5 回呼び出します。
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
メソッドはスレッドがそれに到達する順序で呼び出されます。
.NET