Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье приводятся дополнительные замечания к справочной документации по этому API.
Класс Monitor позволяет синхронизировать доступ к региону кода, захватывая и освобождая блокировку для определенного объекта путем вызова методов Monitor.Enter, Monitor.TryEnter и Monitor.Exit. Блокировки объектов предоставляют возможность ограничить доступ к блоку кода, который обычно называется критическим разделом. Хотя поток удерживает блокировку объекта, ни один другой поток не может получить эту блокировку. Вы также можете использовать Monitor класс, чтобы убедиться, что другой поток не может получить доступ к разделу кода приложения, выполняемого владельцем блокировки, если другой поток не выполняет код с помощью другого заблокированного объекта. Так как класс Monitor обладает принадлежностью к потоку, поток, который захватил блокировку, должен освободить её, вызвав метод Monitor.Exit.
Обзор
Monitor имеет следующие функции:
- Он связан с объектом по запросу.
- Он не привязан к какому-либо контексту, что означает, что его можно вызывать напрямую из любого контекста.
- Невозможно создать экземпляр Monitor класса. Методы Monitor класса являются статическими. Каждый метод передает синхронизированный объект, который управляет доступом к критическому разделу.
Замечание
Используйте класс Monitor для блокировки объектов, отличных от строк (т. е. ссылочных типов, отличных от String, а не типов значений). Дополнительные сведения см. в разделе о перегрузках Enter метода и разделе объекта блокировки далее в этой статье.
В следующей таблице описываются действия, которые могут выполняться потоками, которые обращаются к синхронизированным объектам:
Действие | Описание |
---|---|
Enter, TryEnter | Получает блокировку для объекта. Это действие также знаменует собой начало критического раздела. Ни один другой поток не может ввести критически важный раздел, если он не выполняет инструкции в критическом разделе с использованием другого заблокированного объекта. |
Wait | Освобождает блокировку объекта, чтобы разрешить другим потокам блокировать и получать доступ к объекту. Вызывающий поток ожидает, пока другой поток обращается к объекту. Сигналы пульса используются для уведомления потоков, находящихся в ожидании, об изменениях состояния объекта. |
Pulse (сигнал), PulseAll | Отправляет сигнал одному или нескольким потокам ожидания. Сигнал уведомляет поток ожидания о том, что состояние заблокированного объекта изменилось, и владелец блокировки готов освободить блокировку. Поток ожидания помещается в очередь готовности объекта, чтобы в конечном итоге он мог получить блокировку для объекта. Как только поток получает блокировку, он может проверить текущее состояние объекта, чтобы узнать, достигнуто ли требуемое состояние. |
Exit | Освобождает блокировку объекта. Это действие также помечает конец критического раздела, защищенного заблокированным объектом. |
Существует два набора перегрузок для Enter методов и TryEnter методов. Один набор перегрузок имеет параметр ref
(в C#) или ByRef
(в Visual Basic), который, в случае получения блокировки, атомарно устанавливается в Boolean, даже если при получении блокировки возникает исключение. Используйте эти перегрузки, если крайне важно освободить блокировку во всех ситуациях, даже в том случае, если ресурсы, защищенные блокировкой, могут не находиться в согласованном состоянии.
Объект блокировки
Класс 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. Снова это создает десять новых прямоугольных переменных, которые не зависят друг от друга, 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, чтобы пометить начало и конец критического раздела.
Замечание
Функциональные возможности, предоставляемые Enter и Exit методами, идентичны оператору lock в 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, которые блокируют и освобождают заданный объект, пересекать границы как членов, так и классов, эта практика не рекомендуется.
Пульс, PulseAll и ожидание
После того как поток владеет блокировкой и вошел в критически важный раздел, который защищает блокировка, он может вызывать методы 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 задержку в 2000 миллисекунд. Обратите внимание, что, поскольку 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
вызывается без блокировки в том порядке, в каком потоки его достигают.