Monitor Osztály

Definíció

Olyan mechanizmust biztosít, amely szinkronizálja az objektumokhoz való hozzáférést.

public ref class Monitor abstract sealed
public ref class Monitor sealed
public static class Monitor
public sealed class Monitor
[System.Runtime.InteropServices.ComVisible(true)]
public static class Monitor
type Monitor = class
[<System.Runtime.InteropServices.ComVisible(true)>]
type Monitor = class
Public Class Monitor
Public NotInheritable Class Monitor
Öröklődés
Monitor
Attribútumok

Példák

Az alábbi példa a Monitor osztály használatával szinkronizálja a hozzáférést a Random osztály által képviselt véletlenszám-generátor egyetlen példányához. A példa tíz feladatot hoz létre, amelyek mindegyike aszinkron módon hajtódik végre egy szálkészletben lévő szálon. Minden tevékenység 10 000 véletlenszerű számot hoz létre, kiszámítja az átlagot, és frissíti a két eljárásszintű változót, amelyek a létrehozott véletlenszerű számok és az összegük futó összegét tartják fenn. Az összes tevékenység végrehajtása után a rendszer ezt a két értéket használja a teljes középérték kiszámításához.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   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)

Mivel a szálkészlet-szálon futó bármely tevékenységből elérhetők, a total és n változókhoz való hozzáférést is szinkronizálni kell. A Interlocked.Add metódust erre a célra használják.

Az alábbi példa bemutatja a Monitor osztály (a lock vagy SyncLock nyelvi szerkezettel implementálva), a Interlocked osztály és a AutoResetEvent osztály együttes használatát. Két internal (C#-ban) vagy Friend (Visual Basic)-osztályt határoz meg, SyncResource amelyek UnSyncResourceszinkronizált és nem aszinkron hozzáférést biztosítanak egy erőforráshoz. Annak érdekében, hogy a példa bemutassa a szinkronizált és a nem aszinkron hozzáférés közötti különbséget (ami akkor fordulhat elő, ha az egyes metódushívások gyorsan befejeződnek), a metódus véletlenszerű késleltetést tartalmaz: olyan szálak esetében, amelyek Thread.ManagedThreadId tulajdonsága egyenlő, a metódus 2000 ezredmásodperc késleltetést hív Thread.Sleep meg. Vegye figyelembe, hogy mivel az SyncResource osztály nem nyilvános, egyik ügyfélkód sem zárolja a szinkronizált erőforrást; maga a belső osztály veszi át a zárolást. Ez megakadályozza, hogy a rosszindulatú kód zároljon egy nyilvános objektumot.

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.

A példa egy változót határoz meg, numOpsamely meghatározza az erőforrás eléréséhez megkísérlendő szálak számát. Az alkalmazáslánc ötször hívja meg a ThreadPool.QueueUserWorkItem(WaitCallback) szinkronizált és a nem szinkronizált hozzáférés metódusát. A ThreadPool.QueueUserWorkItem(WaitCallback) metódus egyetlen paraméterrel rendelkezik, egy meghatalmazott, amely nem fogad el paramétereket, és nem ad vissza értéket. Szinkronizált hozzáférés esetén meghívja a SyncUpdateResource metódust; a nem aszinkron hozzáférés esetén meghívja a metódust UnSyncUpdateResource . Minden egyes metódushívás után az alkalmazásszál meghívja az AutoResetEvent.WaitOne metódust, hogy blokkolja a AutoResetEvent példány jelzéséig.

A SyncUpdateResource metódus minden egyes hívása meghívja a belső SyncResource.Access metódust, majd meghívja a Interlocked.Decrement metódust a numOps számláló csökkentésére. A Interlocked.Decrement metódus a számláló dekrementálására szolgál, mert ellenkező esetben nem lehet biztos abban, hogy egy második szál hozzáfér az értékhez, mielőtt az első szál decrementált értékét a változóban tárolnák. Amikor az utolsó szinkronizált feldolgozószál nullára állítja a számlálót, ami azt jelzi, hogy az összes szinkronizált szál hozzáfért az erőforráshoz, a SyncUpdateResource metódus meghívja a EventWaitHandle.Set metódust, amely a fő szálat a végrehajtás folytatására jelzi.

A UnSyncUpdateResource metódus minden egyes hívása meghívja a belső UnSyncResource.Access metódust, majd meghívja a Interlocked.Decrement metódust a numOps számláló csökkentésére. A metódus ismét a Interlocked.Decrement számláló dekrementálására szolgál, hogy egy második szál ne férhessen hozzá az értékhez, mielőtt az első szál decrementált értékét hozzárendelték a változóhoz. Amikor az utolsó nem aszinkron munkaszál nullára állítja a számlálót, ami azt jelzi, hogy nincs szükség több nem szinkronizált szálra az erőforrás eléréséhez, a UnSyncUpdateResource metódus meghívja a EventWaitHandle.Set metódust, amely a fő szálat jelzi a végrehajtás folytatásához.

Ahogy a példa kimenete is mutatja, a szinkronizált hozzáférés biztosítja, hogy a hívószál kilépjen a védett erőforrásból, mielőtt egy másik szál hozzáférhet hozzá; minden szál várakozik az elődjén. Másrészt a zárolás nélkül a UnSyncResource.Access metódust abban a sorrendben hívjuk meg, amelyben a szálak elérik.

Megjegyzések

Az Monitor osztály lehetővé teszi a kód egy régiójához való hozzáférés szinkronizálását egy adott objektum zárolásának felvételével és felszabadításával a Monitor.Enter, Monitor.TryEnterés Monitor.Exit metódusok meghívásával. Az objektumzárak lehetővé teszik a kódblokkokhoz való hozzáférés korlátozását, amelyet gyakran kritikus szakasznak neveznek. Bár egy szál egy objektum zárolását birtokolja, más szál nem szerezheti be ezt a zárolást. Az osztály használatával Monitor azt is biztosíthatja, hogy más szál ne férhessen hozzá a zárolástulajdonos által végrehajtott alkalmazáskód egy szakaszához, kivéve, ha a másik szál egy másik zárolt objektummal hajtja végre a kódot. Mivel a Monitor osztály szál affinitással rendelkezik, a zárolást beszerző szálnak fel kell szabadítania a zárolást a Monitor.Exit metódus meghívásával.

Áttekintés

Monitor a következő funkciókkal rendelkezik:

  • Igény szerinti objektumhoz van társítva.
  • Ez kötetlen, ami azt jelenti, hogy közvetlenül bármely környezetből meghívható.
  • Az osztály egy példánya Monitor nem hozható létre; az Monitor osztály metódusai mind statikusak. A rendszer minden metódust átad a kritikus szakaszhoz való hozzáférést vezérlő szinkronizált objektumnak.

Note

Az osztály használatával a Monitor sztringeken kívül más objektumokat (azaz nem hivatkozástípusokat String) zárolhat, nem értéktípusokat. További részletekért tekintse meg a Enter metódus túlterheléseit és a zárolási objektum szakaszt a cikk későbbi részében.

Az alábbi táblázat a szinkronizált objektumokhoz hozzáférő szálak által végrehajtható műveleteket ismerteti:

Action Description
Enter, TryEnter Egy objektumhoz zárolást szerez. Ez a művelet egy kritikus szakasz kezdetét is jelzi. Más szál csak akkor léphet be a kritikus szakaszba, ha a kritikus szakaszban lévő utasításokat egy másik zárolt objektummal hajtja végre.
Wait Feloldja a zárolást egy objektumon, hogy más szálak zárolhassák és elérhessék az objektumot. A hívószál megvárja, amíg egy másik szál hozzáfér az objektumhoz. A rendszer impulzusjelekkel értesíti a várakozó szálakat az objektum állapotának változásairól.
Pulse (jel), PulseAll Jelet küld egy vagy több várakozó szálnak. A jel értesíti a várakozó szálat, hogy a zárolt objektum állapota megváltozott, és a zárolás tulajdonosa készen áll a zárolás feloldására. A várakozási szál az objektum kész várakozási sorába kerül, hogy végül megszerezhesse az objektum zárolását. Miután a szál megkapta a zárolást, ellenőrizheti az objektum új állapotát, és ellenőrizheti, hogy elérte-e a szükséges állapotot.
Exit Feloldja a zárolást egy objektumon. Ez a művelet a zárolt objektum által védett kritikus szakasz végét is jelzi.

Két túlterhelési csoport van az Enter és a TryEnter metódusok számára. Egy túlterhelési csoport rendelkezik egy ref (C#-ban) vagy ByRef (Visual Basicben) Boolean paraméterrel, amelyet atomilag true-ra állítanak be a zárolás megszerzése esetén, még akkor is, ha kivétel történik a zárolás megszerzésekor. Ezeket a túlterheléseket akkor használja, ha minden esetben kritikus fontosságú a zárolás feloldása, még akkor is, ha a zárolás által védett erőforrások nem konzisztens állapotban vannak.

A zárolási objektum

A Monitor osztály static metódusokból áll (Shared a Visual Basicben), amelyek egy olyan objektumon működnek, amely szabályozza a kritikus szakaszhoz való hozzáférést. Az egyes szinkronizált objektumok esetében a következő információk maradnak fenn:

  • A szálra mutató hivatkozás, amely jelenleg birtokolja a lockot.
  • Egy kész sorra mutató hivatkozás, amely tartalmazza azokat a szálakat, amelyek készen állnak a zárolás megszerzésére.
  • Egy várakozási üzenetsorra mutató hivatkozás, amely tartalmazza azokat a szálakat, amelyek a zárolt objektum állapotának változására várnak.

Monitor objektumokat (azaz hivatkozástípusokat) zárol, nem értéktípusokat. Bár átadhat egy értéktípust mind Enter, mind Exit számára, minden egyes híváshoz külön van boxolva. Mivel minden hívás külön objektumot hoz létre, Enter soha nem blokkol, és a kód, amelyet állítólag véd, valójában nem szinkronizált. Emellett az átadott Exit objektum eltér az átadott Enterobjektumtól, ezért Monitor kivételt SynchronizationLockException jelez az "Objektumszinkronizálási módszer meghívása nem aszinkron kódblokkból" üzenettel.

Az alábbi példa ezt a problémát szemlélteti. A rendszer tíz feladatot indít el, amelyek mindegyike csak 250 milliszekundumig alszik. Minden tevékenység ezután frissít egy számlálóváltozót, nTasksamely a ténylegesen elindított és végrehajtott tevékenységek számának megszámlálására szolgál. Mivel nTasks egy olyan globális változó, amelyet egyszerre több tevékenység is frissíthet, a figyelő több tevékenység egyidejű módosításával szembeni védelmére szolgál. Ahogy azonban a példa kimenete is mutatja, mindegyik feladat kivételt SynchronizationLockException dob.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   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.

Minden tevékenység kivételt SynchronizationLockException jelez, mert a nTasks változó az egyes tevékenységek metódusának Monitor.Enter hívása előtt be van jelölve. Más szóval minden metódushívás egy külön változót ad át, amely független a többiekétől. nTasks ismét dobozolva van a Monitor.Exit metódus hívásában. Ez ismét létrehoz tíz új, egymástól független dobozos változót, nTasksvalamint a metódus hívásában Monitor.Enter létrehozott tíz dobozos változót. A kivételt akkor a rendszer kibocsátja, mert a kód egy korábban nem zárolt, újonnan létrehozott változó zárolását próbálja feloldani.

Habár lehetőség van egy értéktípusú változót dobozolni a következő példában bemutatott módon, majd ugyanazt a dobozolt objektumot mindkét metódusnak továbbadni, ennek nincs előnye. A nem beérkezett változó módosításai nem jelennek meg a dobozos másolatban, és nincs mód a dobozos másolat értékének módosítására.

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.

Ha kiválaszt egy objektumot, amelyen szinkronizálni szeretne, csak privát vagy belső objektumokra kell zárolnia. A külső objektumok zárolása holtpontot eredményezhet, mivel a nem kapcsolódó kód ugyanazokat az objektumokat választhatja ki, amelyek különböző célokra zárolhatók.

Vegye figyelembe, hogy egy objektumot szinkronizálhat több alkalmazástartományban, ha a zároláshoz használt objektum származik a MarshalByRefObject-ból.

A kritikus szakasz

A Enter és Exit metódussal jelölheti egy kritikus szakasz elejét és végét.

Note

A Enter és Exit metódusok által biztosított funkciók megegyeznek a C# lock utasítása és a Visual Basic SyncLock utasítása által biztosított funkciókkal, azzal a különbséggel, hogy a nyelvi szerkezetek a Monitor.Enter(Object, Boolean) metódus túlterhelést és a Monitor.Exit metódust a try ... beburkolják. finally blokkot a monitor kiadásának biztosításához.

Ha a kritikus szakasz egy összefüggő utasítások halmaza, akkor a metódus által Enter beszerzett zárolás garantálja, hogy csak egyetlen szál tudja végrehajtani a zárt kódot a zárolt objektummal. Ebben az esetben azt javasoljuk, hogy a kódot egy try blokkba helyezze, és helyezze el a metódus meghívását Exit egy finally blokkban. Ez biztosítja, hogy a zárolás kivétel esetén is feloldva legyen. A következő kódrészlet ezt a mintát szemlélteti.

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

Ezt a létesítményt általában egy osztály statikus vagy példánymetódusához való hozzáférés szinkronizálására használják.

Ha egy kritikus szakasz egy teljes metódusra terjed ki, a zárolás a System.Runtime.CompilerServices.MethodImplAttribute elhelyezésével valósítható meg a metóduson, és a Synchronized konstruktorban a System.Runtime.CompilerServices.MethodImplAttribute értékének megadásával. Ha ezt az attribútumot használja, a Enter és Exit metódushívásokra nincs szükség. A következő kódrészlet a következő mintát szemlélteti:

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

Vegye figyelembe, hogy az attribútum miatt az aktuális szál addig tartja a zárolást, amíg a metódus vissza nem tér; ha a zárolás hamarabb feloldható, használja az Monitor osztályt, a C# zárolási utasítást vagy a Visual Basic SyncLock utasítást a metóduson belül az attribútum helyett.

Bár lehetséges, hogy az adott objektumot zároló Enter és felszabadító Exit utasítások átíveljenek tag- vagy osztályhatárokon, vagy mindkettőn, ez a gyakorlat nem ajánlott.

Pulse, PulseAll és Wait

Miután egy szál birtokolja a zárolást, és belépett a zárolás által védett kritikus szakaszba, meghívhatja a Monitor.Wait, Monitor.Pulseés Monitor.PulseAll metódusokat.

Amikor a zárolást tartó szál meghívja a Wait, a zárolás feloldódik, és a szál hozzáadódik a szinkronizált objektum várakozási sorához. Ha van kész sorban, a kész várakozási sor első szála megszerzi a zárat, és belép a kritikus szakaszba. A Wait szál a várakozási sorból áthelyeződik a készlet sorba, amikor a zárolást tartó szál meghívja a Monitor.Pulse vagy a Monitor.PulseAll metódust (az áthelyezéshez a szálnak a várakozási sor élén kell lennie). A Wait metódus akkor tér vissza, amikor a hívószál visszaszerzi a zárolást.

Amikor az a szál, amely a zárat tartja, meghívja a Pulse-t, a várakozási sor elején lévő szál átkerül a kész sorba. A metódus PulseAll hívása az összes szálat áthelyezi a várakozási sorból a kész sorba.

Monitorozási és várakozási fogópontok

Fontos megjegyezni a Monitor osztály és a WaitHandle objektumok használata közötti különbséget.

  • Az Monitor osztály tisztán felügyelt, teljes mértékben hordozható, és az operációs rendszer erőforrásigényét tekintve hatékonyabb lehet.
  • WaitHandle Az objektumok az operációs rendszer várakozásra váró objektumait jelölik, hasznosak a felügyelt és a nem felügyelt kód közötti szinkronizáláshoz, és elérhetővé tehetnek néhány speciális operációsrendszer-funkciót, például azt, hogy egyszerre több objektumon is várakozhat.

Tulajdonságok

Name Description
LockContentionCount

Lekérdezi, hogy hányszor volt versengés a monitor zárolásának megkísérlésekor.

Metódusok

Name Description
Enter(Object, Boolean)

Kizárólagos zárolást szerez be a megadott objektumon, és atomilag beállít egy értéket, amely jelzi, hogy a zárolás megtörtént-e.

Enter(Object)

Kizárólagos zárolást szerez be a megadott objektumon.

Exit(Object)

Kizárólagos zárolást ad ki a megadott objektumon.

IsEntered(Object)

Meghatározza, hogy az aktuális szál tartja-e a zárolást a megadott objektumon.

Pulse(Object)

Értesíti a várólistán lévő szálat a zárolt objektum állapotának változásáról.

PulseAll(Object)

Értesíti az objektum állapotában bekövetkező változás minden várakozó szálát.

TryEnter(Object, Boolean)

Megkísérli megszerezni a megadott objektum kizárólagos zárolását, és atomilag beállít egy értéket, amely jelzi, hogy a zárolás megtörtént-e.

TryEnter(Object, Int32, Boolean)

A megadott számú ezredmásodpercben megkísérel kizárólagos zárolást szerezni a megadott objektumon, és atomilag beállít egy értéket, amely jelzi, hogy a zárolás megtörtént-e.

TryEnter(Object, Int32)

A megadott számú ezredmásodpercben megkísérel kizárólagos zárolást szerezni a megadott objektumon.

TryEnter(Object, TimeSpan, Boolean)

Megkísérli a megadott ideig kizárólagos zárolást szerezni a megadott objektumon, és atomilag beállít egy értéket, amely jelzi, hogy a zárolás megtörtént-e.

TryEnter(Object, TimeSpan)

Megkísérli a megadott ideig kizárólagos zárolást szerezni a megadott objektumon.

TryEnter(Object)

Megkísérli a megadott objektum kizárólagos zárolásának beszerzését.

Wait(Object, Int32, Boolean)

Feloldja a zárolást egy objektumon, és letiltja az aktuális szálat, amíg újra meg nem oldja a zárolást. Ha a megadott időtúllépési időköz eltelik, a szál belép a kész üzenetsorba. Ez a módszer azt is meghatározza, hogy a környezet szinkronizálási tartománya (ha szinkronizált környezetben van) a várakozás előtt ki van-e lépve, majd később újból meg legyen-e állítva.

Wait(Object, Int32)

Feloldja a zárolást egy objektumon, és letiltja az aktuális szálat, amíg újra meg nem oldja a zárolást. Ha a megadott időtúllépési időköz eltelik, a szál belép a kész üzenetsorba.

Wait(Object, TimeSpan, Boolean)

Feloldja a zárolást egy objektumon, és letiltja az aktuális szálat, amíg újra meg nem oldja a zárolást. Ha a megadott időtúllépési időköz eltelik, a szál belép a kész üzenetsorba. Ha szeretné, a várakozás előtt kilép a szinkronizálási tartományból a szinkronizált környezethez, majd később újból leküldi a tartományt.

Wait(Object, TimeSpan)

Feloldja a zárolást egy objektumon, és letiltja az aktuális szálat, amíg újra meg nem oldja a zárolást. Ha a megadott időtúllépési időköz eltelik, a szál belép a kész üzenetsorba.

Wait(Object)

Feloldja a zárolást egy objektumon, és letiltja az aktuális szálat, amíg újra meg nem oldja a zárolást.

A következőre érvényes:

Szálbiztonság

Ez a típus szálbiztos.

Lásd még