System.Threading.Monitor, klasa
Ten artykuł zawiera dodatkowe uwagi dotyczące dokumentacji referencyjnej dla tego interfejsu API.
Klasa Monitor umożliwia synchronizowanie dostępu do regionu kodu przez pobranie i zwolnienie blokady dla określonego obiektu przez wywołanie Monitor.Entermetod , Monitor.TryEnteri 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 koligację wątku, wątek, który uzyskał blokadę, musi zwolnić blokadę, wywołując metodę Monitor.Exit.
Omówienie
Monitor ma następujące funkcje:
- Jest on skojarzony z obiektem na żądanie.
- 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żda metoda jest przekazywana zsynchronizowany obiekt, który kontroluje dostęp do sekcji krytycznej.
Uwaga
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 wprowadzić sekcji krytycznej, chyba że wykonuje instrukcje w sekcji krytycznej przy użyciu innego zablokowanego obiektu. |
Wait | Zwalnia blokadę obiektu, aby umożliwić innym wątkom blokowanie i uzyskiwanie dostępu 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 co najmniej jednego wątku oczekującego. Sygnał powiadamia wątek oczekiwania, że stan zablokowanego obiektu uległ zmianie, a właściciel blokady jest gotowy do zwolnienia blokady. Wątek oczekiwania jest umieszczany w kolejce gotowej do użycia obiektu, dzięki czemu może ostatecznie otrzymać blokadę 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 Enter metod i TryEnter . Jeden zestaw przeciążeń ma ref
parametr (w języku C#) lub ByRef
(w Języku Visual Basic), Boolean który jest niepodzieal ustawiony na true
wartość, jeśli blokada zostanie uzyskana, nawet jeśli podczas uzyskiwania blokady zostanie zgłoszony wyjątek. Użyj tych przeciążeń, jeśli ma kluczowe znaczenie dla zwolnienia blokady we wszystkich przypadkach, nawet jeśli zasoby, które chroni blokada, mogą nie być w stanie spójnym.
Obiekt lock
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 przechowuje 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, jest on w polu oddzielnie 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. Ponadto przekazany obiekt Exit różni się od obiektu przekazanego do Enterobiektu , dlatego Monitor zgłasza SynchronizationLockException wyjątek z komunikatem "Metoda synchronizacji obiektów została wywołana z niezsynchronizowanego bloku kodu".
Poniższy przykład ilustruje ten problem. Uruchamia dziesięć zadań, z których każdy po prostu śpi na 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 jak pokazano w danych wyjściowych z przykładu, 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("{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.
Każde zadanie zgłasza SynchronizationLockException wyjątek, ponieważ nTasks
zmienna jest w polu przed wywołaniem Monitor.Enter metody w każdym zadaniu. Innymi słowy każde wywołanie metody jest przekazywane oddzielną zmienną, która jest niezależna od innych. nTasks
element jest ponownie w polu wywołania Monitor.Exit metody . 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 wpisać zmienną typu wartości przed wywołaniem Enter i Exit, jak pokazano w poniższym przykładzie, i przekazać ten sam obiekt boxed do obu metod, nie ma żadnej korzyści, aby to zrobić. Zmiany w zmiennej bez skrzynki nie są odzwierciedlane w kopii skrzynkowej i nie ma możliwości zmiany wartości kopii pola.
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.
Podczas wybierania obiektu, na którym ma zostać zsynchronizowana, należy zablokować tylko na obiektach prywatnych lub wewnętrznych. 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
Enter Użyj metod iExit, aby oznaczyć początek i koniec sekcji krytycznej.
Uwaga
Funkcje udostępniane przez Enter metody i Exit są identyczne z funkcjami podanymi przez instrukcję lock w języku C# i instrukcję SyncLock w Visual Basic, z tą różnicą, że język tworzy Monitor.Enter(Object, Boolean) przeciążenie metody i Monitor.Exit metodę 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
Ta funkcja jest zwykle używana do synchronizowania dostępu do metody statycznej lub wystąpienia klasy.
Jeśli sekcja krytyczna obejmuje całą metodę, obiekt blokady można osiągnąć, umieszczając System.Runtime.CompilerServices.MethodImplAttribute metodę w metodzie i określając Synchronized wartość 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ę do momentu, gdy metoda zwróci wartość ; jeśli blokada może zostać zwolniona wcześniej, użyj Monitor klasy , instrukcji lock języka C# lub instrukcji SyncLock języka Visual Basic wewnątrz metody zamiast atrybutu .
Chociaż istnieje możliwość Enter , aby instrukcje i Exit blokowały i zwalniały dany obiekt do przekraczania granic składowych lub klas lub obu tych elementów, ta praktyka nie jest zalecana.
Pulse, PulseAll i Wait
Gdy wątek jest właścicielem blokady i wprowadzono sekcję krytyczną, którą chroni blokada, może wywołać Monitor.Waitmetody , Monitor.Pulsei Monitor.PulseAll .
Gdy wątek, który przechowuje wywołania Waitblokady, blokada jest zwalniana, a wątek jest dodawany do kolejki oczekiwania zsynchronizowanego obiektu. Pierwszy wątek w gotowej kolejce, jeśli istnieje, uzyskuje blokadę i przechodzi do sekcji krytycznej. 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 jest zwracana, gdy wątek wywołujący ponownie zwraca blokadę.
Gdy wątek, który przechowuje wywołania Pulseblokady , wątek na czele kolejki oczekiwania zostanie przeniesiony do gotowej kolejki. Wywołanie PulseAll metody przenosi wszystkie wątki z kolejki oczekującej do gotowej kolejki.
Monitory i uchwyty oczekiwania
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żdy wykonuje asynchronicznie w wątku 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: {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)
Ponieważ można uzyskać do nich dostęp z dowolnego zadania uruchomionego w wątku puli wątków, należy również zsynchronizować dostęp do zmiennych total
n
. Metoda Interlocked.Add jest używana w tym celu.
W poniższym przykładzie pokazano połączone użycie Monitor klasy (zaimplementowanej z konstrukcją lock
języka lub SyncLock
), Interlocked klasą i klasą 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 upewnić się, że w przykładzie pokazano różnicę między zsynchronizowanym i niezsynchronizowanym dostępem (co może być możliwe w przypadku szybkiego zakończenia wywołania każdej metody), metoda zawiera losowe opóźnienie: dla wątków, których Thread.ManagedThreadId właściwość jest parzysta, wywołania Thread.Sleep metody 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 #{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.
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 SyncUpdateResource
metody wywołuje metodę wewnętrzną SyncResource.Access
, a następnie wywołuje Interlocked.Decrement metodę 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 będzie uzyskiwać dostęp do wartości przed zdekrementowaną wartością pierwszego wątku została zapisana w zmiennej. Gdy ostatni zsynchronizowany wątek procesu roboczego odkreśli licznik do zera, wskazując, że wszystkie zsynchronizowane wątki zakończyły dostęp do zasobu, SyncUpdateResource
metoda wywołuje EventWaitHandle.Set metodę, która sygnalizuje główny wątek, aby kontynuować wykonywanie.
Każde wywołanie UnSyncUpdateResource
metody wywołuje metodę wewnętrzną UnSyncResource.Access
, a następnie wywołuje Interlocked.Decrement metodę w celu dekrementacji licznika numOps
. Po raz kolejny metoda Jest używana do dekrementacji licznika, aby upewnić się, Interlocked.Decrement ż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 odkreśli licznik do zera, co oznacza, że nie ma już niezsynchronizowane wątki muszą uzyskać dostęp do zasobu, UnSyncUpdateResource
metoda wywołuje EventWaitHandle.Set metodę, która sygnalizuje kontynuowanie wykonywania głównego wątku.
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. Z drugiej strony, bez blokady, UnSyncResource.Access
metoda jest wywoływana w kolejności, w której wątki do niego docierają.