Udostępnij za pośrednictwem


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 , 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 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, 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 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 ...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

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 totaln . 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ą , 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 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ą.