本文提供了此 API 参考文档的补充说明。
通过Monitor类,可以通过调用Monitor.EnterMonitor.TryEnterMonitor.Exit和方法获取和释放特定对象的锁来同步对代码区域的访问。 对象锁提供限制对代码块的访问的功能,通常称为关键节。 虽然线程拥有对象的锁,但其他线程无法获取该锁。 还可以使用该 Monitor 类来确保不允许其他线程访问锁所有者正在执行的应用程序代码部分,除非另一个线程使用不同的锁定对象执行代码。 由于 Monitor 类具有线程相关性,因此获取锁的线程必须通过调用 Monitor.Exit 方法释放锁。
概述
Monitor 具有以下功能:
下表描述了访问同步对象的线程可以执行的作:
行动 | DESCRIPTION |
---|---|
Enter、TryEnter | 获取对象的锁定。 此操作也标志着一个关键步骤的开始。 除非其他线程使用不同的锁定对象执行关键节中的指令,否则任何其他线程都不能进入关键节。 |
Wait | 释放对象上的锁,以便允许其他线程锁定和访问该对象。 调用线程在另一个线程访问对象时等待。 脉冲信号用于通知等待线程对对象状态的更改。 |
Pulse (信号), PulseAll | 向一个或多个等待线程发送信号。 该信号通知等待线程锁定对象的状态已更改,锁的所有者已准备好释放锁。 等待线程被放置在对象的就绪队列中,以便它最终可以接收该对象的锁定。 线程具有锁后,可以检查对象的新状态,以查看是否已达到所需状态。 |
Exit | 释放对象上的锁定。 此操作还标记了由锁定对象保护的关键部分的末尾。 |
有两组重载用于 Enter 和 TryEnter 方法。 一组重载具有 ref
(在 C# 中)或 ByRef
(在 Visual Basic 中)Boolean 参数,如果获取锁定,则该参数会原子设置为 true
,即使在获取锁定时会引发异常。 如果在所有情况下释放锁定都至关重要,即使锁定保护的资源可能不处于一致状态,也可以使用这些重载。
该锁定对象
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.
因为 SynchronizationLockException 变量会先于对每个任务中的 nTasks
方法的调用而进行装箱,所以每个任务都将引发 Monitor.Enter 异常。 换句话说,每个方法调用都会传递一个单独的变量,该变量独立于其他变量。
nTasks
在对 Monitor.Exit 方法的调用中会再次进行装箱。 这再次创建十个新的装箱变量,这些变量彼此独立, nTasks
以及调用 Monitor.Enter 方法时创建的十个装箱变量。 然后引发异常,因为我们的代码正在尝试释放以前未锁定的新创建的变量上的锁。
尽管可以先装箱值类型变量,再调用 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 方法提供的功能与 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.PulseMonitor.PulseAll方法。
当持有锁调用 Wait的线程时,将释放该锁,并将该线程添加到同步对象的等待队列中。 就绪队列中的第一个线程(如果有)获取锁定并进入关键区。 当持有锁的线程调用Wait或Monitor.Pulse方法时,调用Monitor.PulseAll的线程会从等待队列移动到就绪队列(要移动的线程必须位于等待队列的头部)。 此方法 Wait 在调用线程重新获取锁时返回。
当持有锁的线程调用 Pulse 时,等待队列头部的线程将被移动到就绪队列。 对方法的调用 PulseAll 会将所有线程从等待队列移动到就绪队列。
监控和等待句柄
请务必注意使用Monitor类与使用WaitHandle对象之间的区别。
- 该 Monitor 类纯粹是托管的、完全可移植的,在操作系统资源需求方面可能更高效。
- WaitHandle 对象表示操作系统可等待的对象,可用于在托管代码和非托管代码之间进行同步,并公开一些高级操作系统功能,如能够同时等待多个对象。
例子
以下示例使用 Monitor 类来同步对由该类表示 Random 的随机数生成器的单个实例的访问。 该示例创建 10 个任务,每个任务在线程池线程上异步执行。 每个任务生成 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) 方法 5 次。
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
方法将按线程到达它的顺序调用。