Класс System.Threading.Monitor
В этой статье приводятся дополнительные замечания к справочной документации по этому API.
Класс Monitor позволяет синхронизировать доступ к региону кода, принимая и выпуская блокировку для определенного объекта, вызывая Monitor.EnterMonitor.TryEnterметоды и Monitor.Exit методы. Блокировки объектов предоставляют возможность ограничить доступ к блоку кода, который обычно называется критическим разделом. Хотя поток владеет блокировкой для объекта, ни один другой поток не может получить этот блок. Вы также можете использовать Monitor класс, чтобы убедиться, что другой поток не может получить доступ к разделу кода приложения, выполняемого владельцем блокировки, если другой поток не выполняет код с помощью другого заблокированного объекта. Так как класс Monitor имеет сходство потоков, поток, полученный блокировкой, должен освободить блокировку, вызвав метод Monitor.Exit.
Обзор
Monitor имеет следующие функции:
- Он связан с объектом по запросу.
- Это означает, что он может вызываться непосредственно из любого контекста.
- Невозможно создать экземпляр Monitor класса. Методы Monitor класса являются статическими. Каждый метод передает синхронизированный объект, который управляет доступом к критическому разделу.
Примечание.
Monitor Используйте класс для блокировки объектов, отличных от строк (т. е. ссылочных типов, отличных Stringот типов значений). Дополнительные сведения см. в разделе о перегрузках Enter метода и разделе объекта блокировки далее в этой статье.
В следующей таблице описываются действия, которые могут выполняться потоками, которые обращаются к синхронизированным объектам:
Действие | Description |
---|---|
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("{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 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("{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 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.
Критический раздел
EnterExit Используйте методы, чтобы пометить начало и конец критического раздела.
Примечание.
Функциональные возможности, предоставляемые Enter методами, Exit идентичными инструкции блокировки в C# и инструкции SyncLock в Visual Basic, за исключением того, что язык создает 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# или инструкцию Visual Basic SyncLock внутри метода вместо атрибута.
Хотя это возможно для Enter операторов Exit , которые блокируют и освобождают заданный объект для пересечения границ членов или классов или обоих, эта практика не рекомендуется.
Пульс, ПульсAll и ожидание
После того как поток владеет блокировкой и вошел в критически важный раздел, который защищает блокировка, он может вызывать Monitor.Waitметоды Monitor.Pulseи Monitor.PulseAll методы.
Когда поток, содержащий вызовы Waitблокировки, освобождается блокировка и поток добавляется в очередь ожидания синхронизированного объекта. Первый поток в очереди готовности, если таковой имеется, получает блокировку и вводит критически важный раздел. Вызываемый Wait поток перемещается из очереди ожидания в готовую очередь, когда Monitor.PulseMonitor.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: {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 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 #{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
, задающая число потоков, которые будут пытаться получить доступ к ресурсу. Поток приложения вызывает метод ThreadPool.QueueUserWorkItem(WaitCallback) для синхронизированного и несинхронизированного доступа по пять раз. Метод ThreadPool.QueueUserWorkItem(WaitCallback) имеет единственный параметр, делегат, который не принимает никаких параметров и не возвращает значений. Для синхронизированного доступа он вызывает метод SyncUpdateResource
; для несинхронизированного доступа он вызывает метод UnSyncUpdateResource
. После каждого набора вызовов метода поток приложения вызывает метод AutoResetEvent.WaitOne , чтобы он блокировался до сигнала экземпляра AutoResetEvent .
Каждый вызов метода SyncUpdateResource
вызывает внутренний метод SyncResource.Access
, а затем вызывает метод Interlocked.Decrement для уменьшения счетчика numOps
. Метод Interlocked.Decrement используется для уменьшения счетчика, так как в противном случае невозможно убедиться, что второй поток получит доступ к значению до того, как в переменной сохранено значение первого потока. Когда последний синхронизированный рабочий поток уменьшает счетчик до нуля, указывая, что все синхронизированные потоки завершили доступ к ресурсу, метод вызывает EventWaitHandle.Set метод, SyncUpdateResource
который сигнализирует основному потоку продолжить выполнение.
Каждый вызов метода UnSyncUpdateResource
вызывает внутренний метод UnSyncResource.Access
, а затем вызывает метод Interlocked.Decrement для уменьшения счетчика numOps
. Еще раз метод используется для уменьшения счетчика, чтобы убедиться, Interlocked.Decrement что второй поток не обращается к значению, прежде чем для переменной назначено отложенное значение первого потока. Когда последний несинхронизированный рабочий поток уменьшает счетчик до нуля, указывая, что больше несинхронизированных потоков не требуется для доступа к ресурсу, метод вызывает метод, UnSyncUpdateResource
который сигнализирует EventWaitHandle.Set основному потоку продолжить выполнение.
Как показывает результат этого примера, синхронизированный доступ обеспечивает, что вызывающий поток выходит из защищенного ресурса до того, как другой поток получит доступ к этому ресурсу; каждый поток ожидает своего предшественника. С другой стороны, без блокировки метод UnSyncResource.Access
вызывается в том порядке, в котором потоки получают к нему доступ.