Sdílet prostřednictvím


System.Threading.Monitor – třída

Tento článek obsahuje doplňující poznámky k referenční dokumentaci pro toto rozhraní API.

Třída Monitor umožňuje synchronizovat přístup k oblasti kódu tím, že vezme a uvolní zámek na konkrétním objektu voláním Monitor.Enter, Monitor.TryEntera Monitor.Exit metody. Zámky objektů umožňují omezit přístup k bloku kódu, který se běžně označuje jako kritická část. Zatímco vlákno vlastní zámek objektu, žádné jiné vlákno nemůže získat tento zámek. Pomocí třídy můžete také Monitor zajistit, aby žádné jiné vlákno nemělo povolený přístup k oddílu kódu aplikace, který provádí vlastník zámku, pokud jiné vlákno nespustí kód pomocí jiného uzamčeného objektu. Protože třída Monitor má spřažení vláken, vlákno, které získalo zámek, musí uvolnit zámek voláním metody Monitor.Exit.

Přehled

Monitor má následující funkce:

  • Je spojen s objektem dle potřeby.
  • Je nevázaný, což znamená, že se dá volat přímo z libovolného kontextu.
  • Instanci Monitor třídy nelze vytvořit. Metody Monitor třídy jsou všechny statické. Každé metodě se předává synchronizovaný objekt, který řídí přístup k kritické části.

Poznámka:

Monitor Třída slouží k uzamčení objektů jiných než řetězců (tj. odkazových typů jiných než String), nikoli hodnotových typů. Podrobnosti naleznete v přetížení metody Enter a v oddílu Objekt uzamčení dále v tomto článku.

Následující tabulka popisuje akce, které mohou provádět vlákna, která přistupují k synchronizovaným objektům:

Činnost Popis
Enter, TryEnter Získá zámek objektu. Tato akce označuje také začátek kritické části. Žádné jiné vlákno nemůže vstoupit do kritického oddílu, ledaže by vykonávalo pokyny v kritickém oddílu pomocí jiného uzamčeného objektu.
Wait Uvolní zámek objektu, aby jiná vlákna mohla objekt uzamknout a získat k němu přístup. Volající vlákno čeká, dokud k objektu přistupuje jiné vlákno. Impulsové signály slouží k upozornění čekacích vláken o změnách stavu objektu.
Pulse (signál), PulseAll Odešle signál do jednoho nebo více čekacích vláken. Signál oznámí čekací vlákno, že se změnil stav uzamčeného objektu a vlastník zámku je připraven ho uvolnit. Čekající vlákno se umístí do fronty připravenosti objektu, aby nakonec mohlo získat zámek objektu. Jakmile má vlákno zámek, může zkontrolovat nový stav objektu a zjistit, jestli byl dosažen požadovaný stav.
Exit Uvolní zámek objektu. Tato akce označuje také konec kritického oddílu chráněného uzamčeným objektem.

Existují dvě sady přetížení pro metody Enter a TryEnter metody. Jedna sada přetížení má ref parametr (v jazyce C#) nebo ByRef (v jazyce Visual Basic), který je atomicky nastaven na Boolean při získání zámku, i když je vyvolána výjimka při pokusu o získání zámku. Použijte tato přetížení, pokud je zásadní uvolnit zámek vždy, dokonce i když prostředky, které zámek chrání, nemusí být ve stále stejném stavu.

Zámek objektu

Třída Monitor se skládá z static metod (Shared v jazyce Visual Basic), které pracují s objektem, který řídí přístup k kritické části. Pro každý synchronizovaný objekt se uchovávají následující informace:

  • Odkaz na vlákno, které aktuálně obsahuje zámek.
  • Odkaz na připravenou frontu, která obsahuje vlákna, která jsou připravená k získání zámku.
  • Odkaz na čekající frontu, která obsahuje vlákna, která čekají na oznámení o změně ve stavu uzamčeného objektu.

Monitor uzamkne objekty (tj. odkazové typy), nikoli typy hodnot. I když můžete předat hodnotový typ do Enter a Exit, je při každém volání znovu zabalen do samostatného objektu. Vzhledem k tomu, že každé volání vytvoří samostatný objekt, Enter nikdy neblokuje a kód, který je údajně chráněn, není ve skutečnosti synchronizován. Kromě toho se objekt předaný Exit liší od objektu předaného Enter, takže Monitor vyvolá SynchronizationLockException výjimku se zprávou "Metoda synchronizace objektů byla volána z nesynchronizovaného bloku kódu."

Následující příklad ukazuje tento problém. Spustí deset úkolů, z nichž každý jen spí po dobu 250 milisekund. Každý úkol pak aktualizuje proměnnou čítače, nTasks, která má spočítat počet úkolů, které se skutečně spustily a vykonaly. Vzhledem k tomu nTasks , že je globální proměnná, kterou lze současně aktualizovat více úlohami, monitor se používá k ochraně před souběžnými úpravami několika úkoly. Jak ale ukazuje výstup z příkladu, každý z úkolů vyvolá SynchronizationLockException výjimku.

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ždý úkol vyvolá SynchronizationLockException výjimku, protože nTasks proměnná je v rámečku před voláním Monitor.Enter metody v každém úkolu. Jinými slovy, při každém volání metody se předává samostatná proměnná, která je nezávislá na ostatních. nTasks je znovu zabalen při volání metody Monitor.Exit. Opět se vytvoří deset nových krabicových proměnných, které jsou nezávislé na sobě, nTasksa deset krabicových proměnných vytvořených ve volání Monitor.Enter metody. Výjimka je vyvolána, a proto se náš kód pokouší uvolnit zámek u nově vytvořené proměnné, která nebyla dříve uzamčena.

I když můžete před voláním Enter a Exit zapouzdřit proměnnou typu hodnoty, jak je znázorněno v následujícím příkladu, a předat stejný zapouzdřený objekt oběma metodám, není žádná výhoda v tom, že to uděláte. Změny neboxované proměnné se neprojeví v zaboxované kopii a neexistuje způsob, jak změnit hodnotu zaboxované kopie.

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.

Při výběru objektu, na kterém se má synchronizovat, byste měli uzamknout pouze soukromé nebo interní objekty. Uzamčení externích objektů může vést k zablokování, protože nesouvisející kód může zvolit stejné objekty, na které se mají uzamknout pro různé účely.

Všimněte si, že u objektu ve více doménách aplikace lze provést synchronizaci, pokud je objekt použitý pro zámek odvozen z MarshalByRefObject.

Kritická část

K označení začátku a konce kritické části použijte metody Enter a Exit metody.

Poznámka:

Funkce poskytované metodami Enter a Exit jsou stejné jako ty, které poskytuje příkaz lock v jazyce C# a příkaz SyncLock v jazyce Visual Basic, s tím rozdílem, že jazykové konstrukce zabalí přetížení metody Monitor.Enter(Object, Boolean) a metodu Monitor.Exit v try... finally blok, aby se zajistilo, že je monitor uvolněn.

Pokud je kritická část sadou souvislých instrukcí, zámek získaný metodou Enter zaručuje, že pouze jedno vlákno může spustit uzavřený kód s uzamčeným objektem. V tomto případě doporučujeme umístit tento kód do try bloku a umístit volání Exit metody do finally bloku. Tím se zajistí uvolnění zámku i v případě, že dojde k výjimce. Tento vzor ilustruje následující fragment kódu.

// 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

Toto zařízení se obvykle používá k synchronizaci přístupu ke statické metodě nebo metodě instance třídy.

Pokud kritický oddíl pokrývá celou metodu, lze uzamčení dosáhnout umístěním System.Runtime.CompilerServices.MethodImplAttribute na metodu a určením Synchronized hodnoty v konstruktoru System.Runtime.CompilerServices.MethodImplAttribute. Pokud použijete tento atribut, volání metod Enter a Exit nejsou potřeba. Tento vzor ilustruje následující fragment kódu:

[MethodImplAttribute(MethodImplOptions.Synchronized)]
void MethodToLock()
{
    // Method implementation.
}
<MethodImplAttribute(MethodImplOptions.Synchronized)>
Sub MethodToLock()
    ' Method implementation.
End Sub

Všimněte si, že atribut způsobí, že aktuální vlákno drží zámek, dokud metoda nevrátí; Pokud lze zámek uvolnit dříve, použijte Monitor třídu, příkaz lock jazyka C# nebo příkaz Visual Basic SyncLock uvnitř metody místo atributu.

I když je možné, aby příkazy Enter a Exit, které zamykají a uvolňují daný objekt, přesáhly členové nebo třídní hranice či obojí, tento postup se nedoporučuje.

Pulse, PulseAll, a Wait

Jakmile vlákno vlastní zámek a vstoupí do kritické sekce, kterou zámek chrání, může volat Monitor.Wait, Monitor.Pulse a Monitor.PulseAll metody.

Když vlákno, které drží zámek, zavolá Wait, zámek se uvolní a vlákno se přidá do fronty čekajících synchronizovaného objektu. První vlákno ve frontě připravených vláken, pokud existuje, získá zámek a přejde do kritické části. Vlákno, které zavolalo Wait, se přesune z čekající fronty do připravené fronty, když je buď metoda Monitor.Pulse, nebo Monitor.PulseAll vykonána vláknem, které drží zámek (aby mohlo být přesunuto, vlákno musí být v čele čekající fronty). Metoda Wait se vrátí, když volající vlákno znovu požaduje zámek.

Když vlákno, které drží zámek, zavolá Pulse, vlákno na začátku čekající fronty se přesune do připravené fronty. Voláním metody PulseAll se přesunou všechna vlákna z čekací fronty do připravené fronty.

Monitory a obslužné rutiny čekání

Je důležité si uvědomit rozdíl mezi použitím Monitor třídy a WaitHandle objektů.

  • Třída Monitor je čistě spravovaná, plně přenosná a může být efektivnější z hlediska požadavků na prostředky operačního systému.
  • WaitHandle objekty představují čekatelné objekty operačního systému, které se hodí k synchronizaci mezi spravovaným a nespravovaným kódem a poskytují přístup k některým pokročilejším funkcím operačního systému, jako je schopnost čekat na mnoho objektů najednou.

Příklady

Následující příklad používá Monitor třídu k synchronizaci přístupu k jedné instanci generátoru náhodných čísel reprezentovaný Random třídou. Příklad vytvoří deset úloh, z nichž každá se spouští asynchronně ve vláknu fondu vláken. Každý úkol generuje 10 000 náhodných čísel, vypočítá jejich průměr a aktualizuje dvě proměnné na úrovni procedury, které udržují průběžný součet počtu vygenerovaných náhodných čísel a jejich součtu. Po provedení všech úkolů se tyto dvě hodnoty použijí k výpočtu celkového průměru.

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)

Vzhledem k tomu, že k nim lze přistupovat z jakékoli úlohy spuštěné ve vlákně fondu vláken, přístup k proměnným total a n musí být také synchronizován. Metoda Interlocked.Add se používá pro tento účel.

Následující příklad ukazuje kombinované použití Monitor třídy (implementované pomocí konstruktoru lock jazyka SyncLock ), Interlocked třídy a AutoResetEvent třídy. Definuje dvě internal třídy (v jazyce C#) nebo Friend (v jazyce Visual Basic) SyncResource a UnSyncResourcekteré poskytují synchronizovaný a nesynchronizovaný přístup k prostředku. Aby se zajistilo, že příklad správně znázorňuje rozdíl mezi synchronizovaným a nesynchronizovaným přístupem (což může nastat, pokud se každé volání metody rychle dokončí), zahrnuje metoda náhodné zpoždění: pro vlákna, jejichž Thread.ManagedThreadId vlastnost je sudá, volá metoda Thread.Sleep pro zavedení zpoždění 2 000 milisekund. Všimněte si, že protože třída SyncResource není veřejná, žádný z klientských kódů nezamyká synchronizovaný prostředek; zámek přebírá samotná interní třída. Tím zabráníte škodlivému kódu převzít zámek na veřejném objektu.

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.

Příklad definuje proměnnou, numOpskterá definuje počet vláken, která se pokusí o přístup k prostředku. Vlákno aplikace volá metodu ThreadPool.QueueUserWorkItem(WaitCallback) pro synchronizovaný a nesynchronizovaný přístup pětkrát každý. Metoda ThreadPool.QueueUserWorkItem(WaitCallback) má jeden parametr, delegát, který nepřijímá žádné parametry a nevrací žádnou hodnotu. Pro synchronizovaný přístup vyvolá metodu SyncUpdateResource , pro nesynchronizovaný přístup vyvolá metodu UnSyncUpdateResource . Po každé sadě volání metody vlákno aplikace zavolá metodu AutoResetEvent.WaitOne tak, že blokuje, dokud nebude signalizována instance AutoResetEvent.

Každé volání metody SyncUpdateResource volá interní metodu SyncResource.Access a potom volá metodu Interlocked.Decrement, která dekrementuje čítač numOps. Metoda Interlocked.Decrement slouží k dekrementování čítače, protože jinak si nemůžete být jisti, že druhé vlákno přistoupí k hodnotě dříve, než bude dekrementovaná hodnota prvního vlákna uložena do proměnné. Když poslední synchronizované pracovní vlákno sníží čítač na nulu, což znamená, že všechna pracovní vlákna dokončila přístup k prostředku, metoda SyncUpdateResource zavolá metodu EventWaitHandle.Set, která signalizuje hlavnímu vláknu, aby pokračovalo.

Každé volání metody UnSyncUpdateResource volá interní metodu UnSyncResource.Access a potom volá metodu Interlocked.Decrement, která dekrementuje čítač numOps. Opět je metoda Interlocked.Decrement použita k dekrementování čítače, aby se zajistilo, že druhé vlákno nemá přístup k hodnotě dříve, než byla dekrementovaná hodnota přiřazena proměnné prvního vlákna. Když poslední nesynchronizované pracovní vlákno sníží čítač na nulu, což znamená, že žádné nesynchronizované vlákna nepotřebují přístup k prostředku, UnSyncUpdateResource metoda volá metodu EventWaitHandle.Set , která signalizuje hlavní vlákno pro pokračování v provádění.

Jak ukazuje výstup z příkladu, synchronizovaný přístup zajistí, že volající vlákno ukončí chráněný prostředek předtím, než k němu bude mít přístup jiné vlákno; každé vlákno čeká na svého předchůdce. Na druhou stranu, bez zámku je metoda UnSyncResource.Access volána v pořadí, ve kterém se vlákna k ní dostanou.