本文提供此 API 參考文件的補充備註。
類別 Monitor 可讓您藉由呼叫 Monitor.Enter、 Monitor.TryEnter和 Monitor.Exit 方法來擷取並釋放特定對象的鎖定,以同步存取程式代碼區域。 對象鎖定可讓您限制對程式碼區塊的存取,通常稱為重要區段。 雖然線程擁有對象的鎖定,但沒有其他線程可以取得該鎖定。 您也可以使用 類別 Monitor 來確保沒有任何其他線程可以存取鎖定擁有者所執行的應用程式程式代碼區段,除非其他線程使用不同的鎖定物件執行程序代碼。 因為 Monitor 類別具有線程親和性,因此取得鎖定的線程必須藉由呼叫 Monitor.Exit 方法來釋放鎖定。
概觀
Monitor 具有下列功能:
- 它會按需與物件相關聯。
- 它是未系結的,這表示可以直接從任何內容呼叫它。
- 無法建立 類別的實例;類別的方法MonitorMonitor都是靜態的。 每個方法都會傳遞同步處理的物件,以控制對重要區段的存取。
下表描述可讓存取同步物件之線程採取的動作:
行動 | 說明 |
---|---|
Enter、TryEnter | 取得物件的鎖定。 此動作也會標示重要區段的開頭。 除非其他線程使用不同的鎖定物件執行重要區段中的指示,否則沒有其他線程可以進入關鍵區段。 |
Wait | 釋放對象的鎖定,以允許其他線程鎖定和存取物件。 呼叫線程會在另一個線程存取 物件時等候。 脈衝訊號可用來通知等候線程有關對象狀態的變更。 |
Pulse (訊號), PulseAll | 將訊號傳送至一或多個等候的線程。 訊號會通知等候線程鎖定物件的狀態已變更,而鎖定的擁有者已準備好釋放鎖定。 等候的線程會放在物件的準備隊列中,以便最終接收物件的鎖。 線程鎖定之後,即可檢查 物件的新狀態,以查看是否已達到所需的狀態。 |
Exit | 釋放物件上的鎖定。 此動作也會標示鎖定物件所保護之重要區段的結尾。 |
Enter 和 TryEnter 方法各有兩組多載。 一組超載具有 ref
(在 C# 中)或 ByRef
(在 Visual Basic 中)Boolean 參數,如果取得鎖定,即便是拋出例外狀況,也會自動設定為 true
。 如果在所有情況下釋放鎖定很重要,請使用這些多載,即使鎖定保護的資源可能不是處於一致狀態也一樣。
鎖定物件
Monitor 類別是由 static
在控制重要區段存取的物件上運作的 (Shared
在 Visual Basic 中) 方法所組成。 每個同步的物件都會儲存以下資訊:
- 目前保留鎖定之線程的參考。
- 就緒佇列的參考,其中包含準備好取得鎖定的線程。
- 等候佇列的參考,其中包含正在等候鎖定對象狀態變更通知的線程。
Monitor 鎖定物件 (也就是參考型別),而不是實值型別。 雖然您可以將值類型傳遞給 Enter 和 Exit,但它會在每次呼叫中分別被封裝。 由於每個呼叫都會建立個別的物件, Enter 因此絕不會封鎖,而且它應該保護的程式代碼不會真正同步處理。 此外,傳遞至 Exit 的對象與傳遞至 Enter的物件不同,因此 Monitor 會 SynchronizationLockException 擲回例外狀況訊息:「物件同步處理方法是從程式代碼的未同步處理區塊呼叫」。
下列範例說明此問題。 它會啟動十項任務,每個任務只會暫停 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.
每個工作都會擲回SynchronizationLockException例外狀況,因為nTasks
變數會在每個工作中呼叫Monitor.Enter方法之前被裝箱。 換句話說,每個方法呼叫都會傳遞與其他人無關的個別變數。
nTasks
在呼叫 Monitor.Exit 方法時,再次進行封箱。 這再次建立十個新的 Boxed 變數,這些變數彼此獨立, nTasks
以及呼叫 Monitor.Enter 方法時建立的十個 Boxed 變數。 接著會擲回例外狀況,因為我們的程式代碼正嘗試釋放先前未鎖定之新建立變數的鎖定。
雖然您可以在呼叫 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 方法取得的鎖定可確保只有一個執行緒可以使用鎖定的物件來執行封閉的程式碼。 在此情況下,我們建議您將該程式代碼放在 區塊中 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# lock 語句或 方法內的 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,000個隨機數、計算其平均值,並更新兩個程式層級變數,以維持產生的隨機數總數及其總和。 執行所有工作之後,就會使用這兩個值來計算整體平均數。
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 類別。 它會定義兩個 internal
(在 C# 中) 或 Friend
(在 Visual Basic) 類別中, SyncResource
以及 UnSyncResource
,分別提供資源的同步處理和未同步存取。 為了確保此範例說明同步處理和未同步存取之間的差異(如果每個方法呼叫快速完成,可能是這種情況),此方法會包含隨機延遲:對於屬性為偶數的線程 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) 方法,分別進行五次同步和未同步存取。 方法 ThreadPool.QueueUserWorkItem(WaitCallback) 具有單一參數,委派不接受任何參數,而且不會傳回任何值。 針對同步存取,它會叫用 SyncUpdateResource
方法;若為未同步存取,則會叫用 UnSyncUpdateResource
方法。 在每組方法呼叫之後,應用程式線程會呼叫 AutoResetEvent.WaitOne 方法,使其封鎖直到 AutoResetEvent 實例發出訊號為止。
每次對 SyncUpdateResource
方法的呼叫,會先呼叫內部 SyncResource.Access
方法,再呼叫 Interlocked.Decrement 方法來遞減 numOps
計數器。
Interlocked.Decrement 方法是用來遞減計數器,因為如果不這樣做,您無法確定第二個執行緒會在第一個執行緒的遞減值儲存在變數中之前存取該值。 當最後一個同步的工作線程將計數器遞減為零時,表示所有同步線程都已完成存取資源,SyncUpdateResource
此方法會呼叫 EventWaitHandle.Set 方法,這個方法會通知主線程繼續執行。
每次對 UnSyncUpdateResource
方法的呼叫,會先呼叫內部 UnSyncResource.Access
方法,再呼叫 Interlocked.Decrement 方法來遞減 numOps
計數器。 同樣地,Interlocked.Decrement 方法被用來遞減計數器,以確保在第一個執行緒的遞減值被指派給變數之前,第二個執行緒不會存取該值。 當最後一個未同步處理的背景工作線程將計數器遞減為零時,表示不再有未同步處理的線程需要存取資源, UnSyncUpdateResource
此方法會呼叫 EventWaitHandle.Set 方法,這個方法會指示主線程繼續執行。
從範例輸出可以看到,同步存取可確保呼叫執行緒會在另一個執行緒存取受保護資源之前結束受保護的資源。每個執行緒會等候其前置任務。 另一方面,如果沒有鎖定,則方法會依照線程到達的順序被呼叫 UnSyncResource.Access
。