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 monitor třídy má spřažení vláken, vlákno, které získal zámek musí uvolnit zámek voláním Monitor.Exit metoda.

Přehled

Monitor má následující funkce:

  • Je přidružen k objektu na vyžádání.
  • 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á metoda 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í Enter metody a lock objekt oddíl 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:

Akce Popis
Enter, TryEnter Získá zámek objektu. Tato akce označuje také začátek kritické části. Žádné jiné vlákno nemůže zadat kritický oddíl, pokud nespouštějí pokyny v kritickém oddílu pomocí jiného uzamčeného objektu.
Wait Uvolní zámek objektu, aby bylo možné uzamknout a získat přístup k objektu jiným vláknům. 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 připravené fronty objektu, aby mohl objekt nakonec přijmout 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), Boolean který je atomicky nastaven na true to, pokud je zámek získán, i když je vyvolán výjimka při získání zámku. Tato přetížení použijte, pokud je důležité uvolnit zámek ve všech případech, i když prostředky, které zámek chrání, nemusí být v konzistentní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 typ Enter hodnoty a Exit, je součástí samostatného rámečku pro každé volání. 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, nTaskskterá má spočítat počet úkolů, které se skutečně spustily a spustily. 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("{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ž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, každé volání metody se předává samostatná proměnná, která je nezávislá na ostatních. nTasks je znovu v volání Monitor.Exit metody. 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 EnterExitzadat proměnnou typu hodnoty a , jak je znázorněno v následujícím příkladu, a předat stejný krabicový objekt oběma metodám, neexistuje žádná výhoda, jak to udělat. Změny neupravené proměnné se neprojeví v krabicové kopii a neexistuje způsob, jak změnit hodnotu v rámečku 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("{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.

Při výběru objektu, na kterém se má synchronizovat, byste měli uzamknout pouze u soukromých nebo interních objektů. 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 jsou Exit stejné jako funkce poskytované příkazem lock v jazyce C# a SyncLock příkaz v jazyce Visual Basic s tím rozdílem, že konstrukty jazyka zabalí Monitor.Enter(Object, Boolean) přetížení metody a metodu Monitor.Exit v try...finally blokování, aby se zajistilo, že je monitorování uvolněno.

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 zařízení uzamčení dosáhnout umístěním System.Runtime.CompilerServices.MethodImplAttribute metody a určením Synchronized hodnoty v konstruktoru System.Runtime.CompilerServices.MethodImplAttribute. Pokud použijete tento atribut, Enter volání metody 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é, Enter aby příkazy a Exit zámky a uvolnění daného objektu překročily hranice členů nebo tříd nebo obojí, tento postup se nedoporučuje.

Pulse, PulseAll a Wait

Jakmile vlákno vlastní zámek a zadá kritický oddíl, který zámek chrání, může volat Monitor.Wait, Monitor.Pulsea Monitor.PulseAll metody.

Když vlákno, které obsahuje volání Waitzámku , zámek se uvolní a vlákno se přidá do čekající fronty synchronizovaného objektu. První vlákno v připravené frontě( pokud existuje), získá zámek a přejde do kritické části. Vlákno, které se volá Wait , se přesune z čekající fronty do připravené fronty, když Monitor.Pulse vlákno nebo Monitor.PulseAll metoda je volána vláknem, které obsahuje zámek (které se má přesunout, 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é obsahuje volání Pulsezámku , vlákno v hlavě čekající fronty se přesune do připravené fronty. Volání PulseAll metody přesune všechna vlákna z čekající 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í objekty s možností čekání operačního systému, jsou užitečné pro synchronizaci mezi spravovaným a nespravovaným kódem a zveřejňují některé pokročilé funkce 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ákně 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: {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)

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 znázorňuje rozdíl mezi synchronizovaným a nesynchronizovaným přístupem (což může být případ, kdy 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ání Thread.Sleep metody zavést zpoždění 2 000 milisekund. Všimněte si, že vzhledem k tomu SyncResource , že třída není veřejná, žádný z klientských kódů nepředjímá zámek synchronizovaného prostředku, vlastní interní třída zámek převezme. Tím zabráníte škodlivému kódu v převzetí zámku u veřejného 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 #{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.

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 volá vlákno aplikace AutoResetEvent.WaitOne metoda tak, aby blokuje, dokud AutoResetEvent instance nebude signalizovat.

Každé volání SyncUpdateResource metody volá interní SyncResource.Access metodu a potom volá metodu Interlocked.DecrementnumOps , která dekrementuje čítač. Metoda Interlocked.Decrement Slouží k dekrementování čítače, protože jinak si nemůžete být jisti, že druhé vlákno bude přistupovat k hodnotě před dekrementovanou hodnotou prvního vlákna byla uložena v proměnné. Když poslední synchronizované pracovní vlákno sníží čítač na nulu, což znamená, že všechna synchronizovaná vlákna dokončila přístup k prostředku, SyncUpdateResource metoda volá metodu EventWaitHandle.Set , která signalizuje hlavní vlákno pokračovat v provádění.

Každé volání UnSyncUpdateResource metody volá interní UnSyncResource.Access metodu a potom volá metodu Interlocked.DecrementnumOps , která dekrementuje čítač. Metoda Je opět použita k dekrementování čítače, aby se zajistilo, Interlocked.Decrement že druhé vlákno nemá přístup k hodnotě před dekrementovanou hodnotou prvního vlákna byla přiřazena proměnné. 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, metoda je volána v pořadí, UnSyncResource.Access ve kterém se vlákna dostanou k němu.