Udostępnij za pośrednictwem


Klasa System.Threading.Monitor

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 , nTasksktó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, nTasksi 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 tryfinally 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ą , numOpsktó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ą.