Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Ten artykuł zawiera dodatkowe uwagi dotyczące dokumentacji referencyjnej dla tego interfejsu API.
Klasa Monitor umożliwia synchronizację dostępu do regionu kodu przez przechwycenie i zwolnienie blokady na określonym obiekcie poprzez wywołanie metod Monitor.Enter, Monitor.TryEnter i Monitor.Exit. Blokady obiektów zapewniają możliwość ograniczenia dostępu do bloku kodu, często nazywanego sekcją krytyczną. Chociaż wątek jest właścicielem blokady dla obiektu, żaden inny wątek nie może uzyskać tej blokady. Możesz również użyć Monitor klasy , aby upewnić się, że żaden inny wątek nie może uzyskać dostępu do sekcji kodu aplikacji wykonywanej przez właściciela blokady, chyba że inny wątek wykonuje kod przy użyciu innego zablokowanego obiektu. Ponieważ klasa Monitor ma powiązanie z wątkiem, wątek, który uzyskał blokadę, musi zwolnić blokadę, wywołując metodę Monitor.Exit.
Przegląd
Monitor ma następujące funkcje:
- Jest on skojarzony z obiektem na życzenie.
- Jest on niezwiązany, co oznacza, że może być wywoływany bezpośrednio z dowolnego kontekstu.
- Nie można utworzyć wystąpienia Monitor klasy; metody Monitor klasy są statyczne. Każdej metodzie jest przekazywany zsynchronizowany obiekt, który kontroluje dostęp do sekcji krytycznej.
Uwaga / Notatka
Monitor Użyj klasy , aby zablokować obiekty inne niż ciągi (czyli typy odwołań inne niż String), a nie typy wartości. Aby uzyskać szczegółowe informacje, zobacz przeciążenia Enter metody i sekcję obiektu blokady w dalszej części tego artykułu.
W poniższej tabeli opisano akcje, które mogą być wykonywane przez wątki, które uzyskują dostęp do zsynchronizowanych obiektów:
Akcja | Opis |
---|---|
Enter, TryEnter | Uzyskuje blokadę dla obiektu. Ta akcja oznacza również początek sekcji krytycznej. Żaden inny wątek nie może wejść do sekcji krytycznej, chyba że wykonuje instrukcje w tej sekcji przy użyciu innego zablokowanego obiektu. |
Wait | Zwalnia blokadę obiektu, aby inne wątki mogły je zablokować i uzyskać dostęp do obiektu. Wątek wywołujący czeka, gdy inny wątek uzyskuje dostęp do obiektu. Sygnały impulsowe służą do powiadamiania wątków oczekujących o zmianach stanu obiektu. |
Pulse (sygnał), PulseAll | Wysyła sygnał do jednego lub więcej oczekujących wątków. Sygnał powiadamia wątek oczekiwania, że stan zablokowanego obiektu uległ zmianie, a właściciel blokady jest gotowy do zwolnienia blokady. Wątek czekający jest umieszczany w kolejce gotowości obiektu, dzięki czemu może ostatecznie uzyskać dostęp do blokady obiektu. Gdy wątek ma blokadę, może sprawdzić nowy stan obiektu, aby sprawdzić, czy został osiągnięty wymagany stan. |
Exit | Zwalnia blokadę obiektu. Ta akcja oznacza również koniec sekcji krytycznej chronionej przez zablokowany obiekt. |
Istnieją dwa zestawy przeciążeń dla metod Enter i TryEnter. Jeden zestaw przeciążeń ma ref
parametr (w języku C#) lub ByRef
(w języku Visual Basic), Boolean który jest niepodzielnie ustawiony na true
, jeśli blokada zostanie nabyta, nawet jeśli podczas uzyskiwania blokady zostanie zgłoszony wyjątek. Użyj tych przeciążeń, jeśli zwolnienie blokady we wszystkich przypadkach ma kluczowe znaczenie, nawet jeśli zasoby, które chroni blokada, mogą nie być w stanie spójnym.
Obiekt zamknięcia
Klasa Monitor składa się z static
metod (Shared
w Visual Basic), które działają na obiekcie, który kontroluje dostęp do sekcji krytycznej. Dla każdego zsynchronizowanego obiektu są przechowywane następujące informacje:
- Odwołanie do wątku, który obecnie dzierży blokadę.
- Odwołanie do gotowej kolejki, która zawiera wątki, które są gotowe do uzyskania blokady.
- Odwołanie do kolejki oczekującej, która zawiera wątki oczekujące na powiadomienie o zmianie stanu zablokowanego obiektu.
Monitor blokuje obiekty (czyli typy odwołań), a nie typy wartości. Mimo że można przekazać typ wartości do Enter i Exit, zostaje on zapakowany osobno dla każdego wywołania. Ponieważ każde wywołanie tworzy oddzielny obiekt, Enter nigdy nie blokuje, a kod, który rzekomo chroni, nie jest naprawdę zsynchronizowany. pl-PL: Ponadto obiekt przekazany do Exit różni się od obiektu przekazanego do Enter, dlatego Monitor zgłasza wyjątek SynchronizationLockException z komunikatem "Metoda synchronizacji obiektów została wywołana z niesynchronizowanego bloku kodu."
Poniższy przykład ilustruje ten problem. Uruchamia dziesięć zadań, z których każde po prostu czeka przez 250 milisekund. Każde zadanie aktualizuje następnie zmienną licznika , nTasks
która ma liczyć liczbę zadań, które faktycznie uruchomione i wykonane. Ponieważ nTasks
jest zmienną globalną, która może być aktualizowana przez wiele zadań jednocześnie, monitor jest używany do ochrony przed równoczesną modyfikacją przez wiele zadań. Jednak dane wyjściowe z przykładu pokazują, że każde z zadań zgłasza SynchronizationLockException wyjątek.
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.
Każde zadanie zgłasza SynchronizationLockException wyjątek, ponieważ nTasks
zmienna jest boksowana przed wywołaniem metody Monitor.Enter. Innymi słowy każde wywołanie metody jest przekazywane oddzielną zmienną, która jest niezależna od innych.
nTasks
jest ponownie zapakowany podczas wywołania metody Monitor.Exit. Po raz kolejny spowoduje to utworzenie dziesięciu nowych zmiennych skrzynkowych, które są niezależne od siebie, nTasks
i dziesięć zmiennych skrzynkowych utworzonych w wywołaniu Monitor.Enter metody . Zgłaszany jest wyjątek, ponieważ nasz kod próbuje zwolnić blokadę dla nowo utworzonej zmiennej, która nie została wcześniej zablokowana.
Mimo że można opakować zmienną typu wartości przed wywołaniem Enter i Exit, jak pokazano w poniższym przykładzie, i przekazać ten sam obiekt opakowany do obu metod, nie przynosi to żadnych korzyści. Zmiany w niezapakowanej zmiennej nie są odzwierciedlane w zapakowanej kopii i nie ma możliwości zmiany wartości zapakowanej kopii.
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.
Podczas wybierania obiektu, na którym chcesz synchronizować, należy blokować tylko na prywatnych lub wewnętrznych obiektach. Blokowanie obiektów zewnętrznych może spowodować zakleszczenia, ponieważ niepowiązany kod może wybrać te same obiekty do zablokowania w różnych celach.
Należy pamiętać, że można zsynchronizować obiekt w wielu domenach aplikacji, jeśli obiekt używany do blokady pochodzi z MarshalByRefObject.
Sekcja krytyczna
Użyj metody Enter i Exit do oznaczenia początku i końca sekcji krytycznej.
Uwaga / Notatka
Funkcjonalność zapewniana przez metody Enter i Exit jest identyczna z funkcjonalnością zapewnianą przez instrukcję lock w języku C# i instrukcję SyncLock w Visual Basic, z tą różnicą, że konstrukcje językowe opakowują przeciążenie metody Monitor.Enter(Object, Boolean) i metodę Monitor.Exit w try
…
finally
blokuj, aby upewnić się, że monitor jest zwolniony.
Jeśli sekcja krytyczna jest zestawem ciągłych instrukcji, blokada uzyskana przez Enter metodę gwarantuje, że tylko jeden wątek może wykonać zamknięty kod z zablokowanym obiektem. W takim przypadku zalecamy umieszczenie tego kodu w try
bloku i umieszczenie wywołania Exit metody w finally
bloku. Dzięki temu blokada zostanie zwolniona, nawet jeśli wystąpi wyjątek. Poniższy fragment kodu ilustruje ten wzorzec.
// 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
Ten mechanizm jest zwykle używany do synchronizowania dostępu do metody statycznej lub metody instancji klasy.
Jeśli sekcja krytyczna obejmuje całą metodę, mechanizm blokujący można osiągnąć, umieszczając System.Runtime.CompilerServices.MethodImplAttribute na metodzie i określając wartość Synchronized w konstruktorze klasy System.Runtime.CompilerServices.MethodImplAttribute. Jeśli używasz tego atrybutu Enter , wywołania metod i Exit nie są potrzebne. Poniższy fragment kodu ilustruje ten wzorzec:
[MethodImplAttribute(MethodImplOptions.Synchronized)]
void MethodToLock()
{
// Method implementation.
}
<MethodImplAttribute(MethodImplOptions.Synchronized)>
Sub MethodToLock()
' Method implementation.
End Sub
Należy pamiętać, że atrybut powoduje, że bieżący wątek będzie przechowywać blokadę dopóki metoda nie zwróci; jeśli blokada może zostać zwolniona wcześniej, użyj klasy Monitor, instrukcji lock języka C# lub instrukcji SyncLock języka Visual Basic wewnątrz metody zamiast atrybutu.
Chociaż istnieje możliwość, że instrukcje Enter i Exit, które blokują i zwalniają dany obiekt, mogą przekraczać granice składowych lub klas, lub obu tych elementów, to taka praktyka nie jest zalecana.
Pulse, PulseAll i Wait
Gdy wątek posiada blokadę i wchodzi do chronionej przez nią sekcji krytycznej, może wywołać metody Monitor.Wait, Monitor.Pulse i Monitor.PulseAll.
Gdy wątek, który utrzymuje blokadę, wywołuje Wait, blokada zostaje zwolniona, a wątek jest dodawany do kolejki oczekujących zsynchronizowanego obiektu. Pierwszy wątek w gotowej kolejce uzyskuje blokadę i przechodzi do sekcji krytycznej, jeśli istnieje. Wątek, który jest wywoływany Wait , jest przenoszony z kolejki oczekującej do kolejki gotowej, gdy Monitor.Pulse metoda lub Monitor.PulseAll jest wywoływana przez wątek, który przechowuje blokadę (aby zostać przeniesiony, wątek musi znajdować się na czele kolejki oczekującej). Metoda Wait zwraca, gdy wątek wywołujący ponownie przejmuje blokadę.
Gdy wątek, który utrzymuje blokadę, wywoła Pulse, wątek na czele kolejki oczekiwania zostanie przeniesiony do kolejki gotowości. Wywołanie metody PulseAll przenosi wszystkie wątki z kolejki oczekującej do kolejki gotowej.
Monitorowanie i uchwyty synchronizacji
Należy pamiętać o różnicy między użyciem Monitor klasy i WaitHandle obiektów.
- Klasa Monitor jest czysto zarządzana, w pełni przenośna i może być bardziej wydajna pod względem wymagań dotyczących zasobów systemu operacyjnego.
- WaitHandle obiekty reprezentują obiekty oczekujące na system operacyjny, są przydatne do synchronizowania między kodem zarządzanym i niezarządzanym oraz uwidaczniają niektóre zaawansowane funkcje systemu operacyjnego, takie jak możliwość oczekiwania na wiele obiektów jednocześnie.
Przykłady
W poniższym przykładzie użyto Monitor klasy do zsynchronizowania dostępu do pojedynczego wystąpienia generatora liczb losowych reprezentowanego przez klasę Random . W przykładzie tworzonych jest dziesięć zadań, z których każde wykonywane jest asynchronicznie na wątku w puli wątków. Każde zadanie generuje 10 000 liczb losowych, oblicza ich średnią i aktualizuje dwie zmienne na poziomie procedury, które utrzymują łączną liczbę wygenerowanych liczb losowych i ich sumę. Po wykonaniu wszystkich zadań te dwie wartości są następnie używane do obliczenia ogólnej średniej.
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)
Ponieważ można uzyskać dostęp do zmiennych total
i n
z dowolnego zadania uruchomionego w wątku działającym w puli wątków, należy również zsynchronizować dostęp do tych zmiennych. Metoda Interlocked.Add jest używana w tym celu.
W poniższym przykładzie pokazano użycie klasy Monitor (zaimplementowanej przy użyciu konstrukcji języka lock
lub SyncLock
), klasy Interlocked oraz klasy AutoResetEvent. Definiuje dwie internal
klasy (w języku C#) lub Friend
(w Visual Basic) SyncResource
i UnSyncResource
, które zapewniają odpowiednio zsynchronizowany i niezsynchronizowany dostęp do zasobu. Aby zagwarantować, że w przykładzie zademonstrowano różnicę między zsynchronizowanym i niezsynchronizowanym dostępem (co może mieć miejsce, jeśli każde wywołanie metody kończy się szybko), metoda zawiera losowe opóźnienie: dla wątków, których Thread.ManagedThreadId właściwość jest parzysta, metoda wywołuje Thread.Sleep w celu wprowadzenia opóźnienia 2000 milisekund. Należy pamiętać, że ponieważ SyncResource
klasa nie jest publiczna, żaden z kodu klienta nie przyjmuje blokady na zsynchronizowanym zasobie; sama klasa wewnętrzna przyjmuje blokadę. Uniemożliwia to złośliwemu kodowi zablokowanie obiektu publicznego.
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.
W przykładzie zdefiniowano zmienną , numOps
która definiuje liczbę wątków, które będą próbowały uzyskać dostęp do zasobu. Wątek aplikacji wywołuje metodę ThreadPool.QueueUserWorkItem(WaitCallback) synchronizowania i niezsynchronizowanego dostępu pięć razy. Metoda ThreadPool.QueueUserWorkItem(WaitCallback) ma jeden parametr, delegat, który nie akceptuje żadnych parametrów i nie zwraca żadnej wartości. W przypadku dostępu zsynchronizowanej wywołuje metodę SyncUpdateResource
. W przypadku dostępu niezsynchronizowanego wywołuje metodę UnSyncUpdateResource
. Po każdym zestawie wywołań metody wątek aplikacji wywołuje metodę AutoResetEvent.WaitOne , tak aby blokowała AutoResetEvent do momentu zasygnaliowania wystąpienia.
Każde wywołanie metody SyncUpdateResource
wywołuje wewnętrzną metodę SyncResource.Access
, a następnie metodę Interlocked.Decrement w celu dekrementacji licznika numOps
. Metoda Interlocked.Decrement służy do dekrementacji licznika, ponieważ w przeciwnym razie nie można mieć pewności, że drugi wątek uzyska dostęp do wartości, zanim zdekrementowana wartość pierwszego wątku zostanie zapisana w zmiennej. Gdy ostatni zsynchronizowany wątek roboczy zredukuje licznik do zera, wskazując, że wszystkie zsynchronizowane wątki zakończyły dostęp do zasobu, metoda SyncUpdateResource
wywołuje metodę EventWaitHandle.Set, która sygnalizuje głównemu wątkowi kontynuowanie wykonywania.
Każde wywołanie metody UnSyncUpdateResource
wywołuje wewnętrzną metodę UnSyncResource.Access
, a następnie metodę Interlocked.Decrement w celu dekrementacji licznika numOps
. Po raz kolejny metoda Interlocked.Decrement jest używana do dekrementacji licznika, aby upewnić się, że drugi wątek nie uzyskuje dostępu do wartości przed przypisaniem wartości zdekrementowanej pierwszego wątku do zmiennej. Gdy ostatni niezsynchronizowany wątek procesu roboczego zmniejszy licznik do zera, co oznacza, że żadne niezsynchronizowane wątki nie muszą już uzyskiwać dostępu do zasobu, metoda UnSyncUpdateResource
wywołuje metodę EventWaitHandle.Set, która sygnalizuje głównemu wątkowi kontynuowanie wykonywania.
Jak pokazano w danych wyjściowych z przykładu, zsynchronizowany dostęp gwarantuje, że wątek wywołujący zamyka chroniony zasób, zanim inny wątek będzie mógł uzyskać do niego dostęp; każdy wątek czeka na swojego poprzednika. Natomiast bez blokady metoda UnSyncResource.Access
jest wywoływana w kolejności, w której wątki do niej docierają.