Monitore

Monitor-Objekte ermöglichen den synchronen Zugriff auf eine Coderegion, indem mithilfe der Methoden Monitor.Enter, Monitor.TryEnter und Monitor.Exiteine Sperre für ein bestimmtes Objekt erstellt und freigegeben wird. Sobald eine Sperre für einen Codebereich erstellt wurde, können Sie die Methoden Monitor.Wait, Monitor.Pulse und Monitor.PulseAllverwenden. Waitgibt die Sperre frei, falls diese gehalten wird und auf eine Benachrichtigung wartet. Bei einer Benachrichtigung wird Wait beendet, und die Sperre wird erneut erstellt. Sowohl Pulse als auchPulseAll signalisieren, dass der nächste Thread in der Warteschlange verarbeitet werden kann.

Die SyncLock-Anweisung in Visual Basic und die lock-Anweisung in C# verwenden Monitor.Enter, um die Sperre zu übernehmen, und Monitor.Exit, um die Sperre freizugeben. Der Vorteil von Programmiersprachenanweisungen besteht darin, dass der gesamte Inhalt im lock-Block bzw. SyncLock-Block in einer Try-Anweisung enthalten ist. Die Try-Anweisung verfügt über einen Finally-Block, mit dem sichergestellt wird, dass die Sperre aufgehoben wird.

Monitor sperrt Objekte (d. h. Referenztypen), nicht Werttypen. Sie können einen Werttyp an Enter und Exit übergeben. Dieser wird jedoch bei jedem Aufruf separat geschachtelt. Da jeder Aufruf ein separates Objekt erstellt, wird Enter nie blockiert, und der Code, der eigentlich geschützt werden soll, wird nicht wirklich synchronisiert. Darüber hinaus unterscheiden sich das an Exit und das an Enter übergebene Objekt, sodass Monitor eine SynchronizationLockException mit der folgenden Meldung auslöst: "Die Objektsynchronisierungsmethode wurde von einem nicht synchronisierten Codeblock aufgerufen". Das folgende Beispiel verdeutlicht diese Probleme.

Try
    Dim x As Integer = 1
    ' The call to Enter() creates a generic synchronizing object for the value
    ' of x each time the code is executed, so that Enter never blocks.
    Monitor.Enter(x)
    Try
        ' Code that needs to be protected by the monitor.
    Finally
        ' Always use Finally to ensure that you exit the Monitor.

        ' The call to Exit() will FAIL!!!
        ' The synchronizing object created for x in Exit() will be different
        ' than the object used in Enter(). SynchronizationLockException
        ' will be thrown.
        Monitor.Exit(x)
    End Try
Catch SyncEx As SynchronizationLockException
    Console.WriteLine("A SynchronizationLockException occurred. Message:")
    Console.WriteLine(SyncEx.Message)
End Try
try
{
    int x = 1;
    // The call to Enter() creates a generic synchronizing object for the value
    // of x each time the code is executed, so that Enter never blocks.
    Monitor.Enter(x);
    try
    {
        // Code that needs to be protected by the monitor.
    }
    finally
    {
        // Always use Finally to ensure that you exit the Monitor.

        // The call to Exit() will FAIL!!!
        // The synchronizing object created for x in Exit() will be different
        // than the object used in Enter(). SynchronizationLockException
        // will be thrown.
        Monitor.Exit(x);
    }
}
catch (SynchronizationLockException SyncEx)
{
    Console.WriteLine("A SynchronizationLockException occurred. Message:");
    Console.WriteLine(SyncEx.Message);
}
try
{
    int x = 1;
    // The call to Enter() creates a generic synchronizing object for the value
    // of x each time the code is executed, so that Enter never blocks.
    Monitor::Enter(x);
    try
    {
        // Code that needs to be protected by the monitor.
    }
    finally
    {
        // Always use Finally to ensure that you exit the Monitor.

        // The call to Exit() will FAIL!!!
        // The synchronizing object created for x in Exit() will be different
        // than the object used in Enter(). SynchronizationLockException
        // will be thrown.
        Monitor::Exit(x);
    }
}
catch (SynchronizationLockException^ SyncEx)
{
    Console::WriteLine("A SynchronizationLockException occurred. Message:");
    Console::WriteLine(SyncEx->Message);
}

Wie das folgende Beispiel zeigt, können Sie eine Werttypvariable zwar vor dem Aufruf von Enter und Exit schachteln und dasselbe geschachtelte Objekt an beide Methoden übergeben, diese Vorgehensweise bietet jedoch keinerlei Vorteile. Änderungen an der Variable wirken sich nicht auf die geschachtelte Kopie aus, und es ist nicht möglich, den Wert in der geschachtelten Kopie zu ändern.

Dim x As Integer = 1
Dim o As object = x

Monitor.Enter(o)
Try
    ' Code that needs to be protected by the monitor.
Finally
    ' Always use Finally to ensure that you exit the Monitor.
    Monitor.Exit(o)
End Try
int x = 1;
object o = x;

Monitor.Enter(o);
try
{
    // Code that needs to be protected by the monitor.
}
finally
{
    // Always use Finally to ensure that you exit the Monitor.
    Monitor.Exit(o);
}
int x = 1;
Object^ o = x;

Monitor::Enter(o);
try
{
    // Code that needs to be protected by the monitor.
}
finally
{
    // Always use Finally to ensure that you exit the Monitor.
    Monitor::Exit(o);
}

Es ist wichtig, die Unterschiede in der Verwendung des Monitor-Objekts und des WaitHandle-Objekts zu beachten. Monitor-Objekte sind rein verwaltete, vollständig übertragbare Objekte, die in Bezug auf die Ressourcenanforderungen des Betriebssystems effizienter sein können. WaitHandle-Objekte repräsentieren Objekte des Betriebssystems, die in der Lage sind, ihre Ausführung zu unterbrechen und zu warten. Sie sind für die Synchronisierung zwischen verwaltetem und nicht verwaltetem Code von Nutzen und verfügen über einige höhere Betriebssystemfunktionen, z. B. die Fähigkeit, auf viele Objekte gleichzeitig zu warten.

Im folgenden Codebeispiel wird die gemeinsame Verwendung der Monitor-Klasse (implementiert mit der lock-Compileranweisung und der SyncLock-Compileranweisung), der Interlocked-Klasse und der AutoResetEvent-Klasse veranschaulicht.

Imports System
Imports System.Threading

' Note: The class whose internal public member is the synchronizing
' method is not public; none of the client code takes a lock on the
' Resource object.The member of the nonpublic class takes the lock on
' itself. Written this way, malicious code cannot take a lock on
' a public object.
Class SyncResource
    Public Sub Access(threadNum As Int32)
        ' Uses Monitor class to enforce synchronization.
        SyncLock Me
            ' Synchronized: Despite the next conditional, each thread
            ' waits on its predecessor.
            If threadNum Mod 2 = 0 Then
                Thread.Sleep(2000)
            End If
            Console.WriteLine("Start Synched Resource access (Thread={0})", threadNum)
            Thread.Sleep(200)
            Console.WriteLine("Stop Synched Resource access  (Thread={0})", threadNum)
        End SyncLock
    End Sub
End Class

' Without the lock, the method is called in the order in which threads reach it.
Class UnSyncResource
    Public Sub Access(threadNum As Int32)
        ' Does not use Monitor class to enforce synchronization.
        ' The next call throws the thread order.
        If threadNum Mod 2 = 0 Then
            Thread.Sleep(2000)
        End If
        Console.WriteLine("Start UnSynched Resource access (Thread={0})", threadNum)
        Thread.Sleep(200)
        Console.WriteLine("Stop UnSynched Resource access  (Thread={0})", threadNum)
    End Sub
End Class

Public Class App
    Private Shared numAsyncOps As Int32 = 5
    Private Shared asyncOpsAreDone As New AutoResetEvent(false)
    Private Shared SyncRes As New SyncResource()
    Private Shared UnSyncRes As New UnSyncResource()

    Public Shared Sub Main()
        For threadNum As Int32 = 0 To 4
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf SyncUpdateResource), threadNum)
        Next threadNum

        ' Wait until this WaitHandle is signaled.
        asyncOpsAreDone.WaitOne()
        Console.WriteLine(vbTab + vbNewLine + "All synchronized operations have completed." + vbTab + vbNewLine)

        ' Reset the thread count for unsynchronized calls.
        numAsyncOps = 5

        For threadNum As Int32 = 0 To 4
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf UnSyncUpdateResource), threadNum)
        Next threadNum

        ' Wait until this WaitHandle is signaled.
        asyncOpsAreDone.WaitOne()
        Console.WriteLine("\t\nAll unsynchronized thread operations have completed.")
    End Sub

    ' The callback method's signature MUST match that of a
    ' System.Threading.TimerCallback delegate (it takes an Object
    ' parameter and returns void).
    Shared Sub SyncUpdateResource(state As Object)
        ' This calls the internal synchronized method, passing
        ' a thread number.
        SyncRes.Access(CType(state, Int32))

        ' Count down the number of methods that the threads have called.
        ' This must be synchronized, however; you cannot know which thread
        ' will access the value **before** another thread's incremented
        ' value has been stored into the variable.
        If Interlocked.Decrement(numAsyncOps) = 0 Then
            ' Announce to Main that in fact all thread calls are done.
            asyncOpsAreDone.Set()
        End If
    End Sub

    ' The callback method's signature MUST match that of a
    ' System.Threading.TimerCallback delegate (it takes an Object
    ' parameter and returns void).
    Shared Sub UnSyncUpdateResource(state As Object)
        ' This calls the unsynchronized method, passing a thread number.
        UnSyncRes.Access(CType(state, Int32))

        ' Count down the number of methods that the threads have called.
        ' This must be synchronized, however; you cannot know which thread
        ' will access the value **before** another thread's incremented
        ' value has been stored into the variable.
        If Interlocked.Decrement(numAsyncOps) = 0 Then
            ' Announce to Main that in fact all thread calls are done.
            asyncOpsAreDone.Set()
        End If
    End Sub
End Class
using System;
using System.Threading;

// Note: The class whose internal public member is the synchronizing
// method is not public; none of the client code takes a lock on the
// Resource object.The member of the nonpublic class takes the lock on
// itself. Written this way, malicious code cannot take a lock on
// a public object.
class SyncResource
{
    public void Access(Int32 threadNum)
    {
        // Uses Monitor class to enforce synchronization.
        lock (this)
        {
            // Synchronized: Despite the next conditional, each thread
            // waits on its predecessor.
            if (threadNum % 2 == 0)
            {
                Thread.Sleep(2000);
            }
            Console.WriteLine("Start Synched Resource access (Thread={0})", threadNum);
            Thread.Sleep(200);
            Console.WriteLine("Stop Synched Resource access  (Thread={0})", threadNum);
        }
    }
}

// Without the lock, the method is called in the order in which threads reach it.
class UnSyncResource
{
    public void Access(Int32 threadNum)
    {
        // Does not use Monitor class to enforce synchronization.
        // The next call throws the thread order.
        if (threadNum % 2 == 0)
        {
            Thread.Sleep(2000);
        }
        Console.WriteLine("Start UnSynched Resource access (Thread={0})", threadNum);
        Thread.Sleep(200);
        Console.WriteLine("Stop UnSynched Resource access  (Thread={0})", threadNum);
    }
}

public class App
{
    static Int32 numAsyncOps = 5;
    static AutoResetEvent asyncOpsAreDone = new AutoResetEvent(false);
    static SyncResource SyncRes = new SyncResource();
    static UnSyncResource UnSyncRes = new UnSyncResource();

    public static void Main()
    {
        for (Int32 threadNum = 0; threadNum < 5; threadNum++)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(SyncUpdateResource), threadNum);
        }

        // Wait until this WaitHandle is signaled.
        asyncOpsAreDone.WaitOne();
        Console.WriteLine("\t\nAll synchronized operations have completed.\t\n");

        // Reset the thread count for unsynchronized calls.
        numAsyncOps = 5;

        for (Int32 threadNum = 0; threadNum < 5; threadNum++)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(UnSyncUpdateResource), threadNum);
        }

        // Wait until this WaitHandle is signaled.
        asyncOpsAreDone.WaitOne();
        Console.WriteLine("\t\nAll unsynchronized thread operations have completed.");
    }

    // The callback method's signature MUST match that of a
    // System.Threading.TimerCallback delegate (it takes an Object
    // parameter and returns void).
    static void SyncUpdateResource(Object state)
    {
        // This calls the internal synchronized method, passing
        // a thread number.
        SyncRes.Access((Int32) state);

        // Count down the number of methods that the threads have called.
        // This must be synchronized, however; you cannot know which thread
        // will access the value **before** another thread's incremented
        // value has been stored into the variable.
        if (Interlocked.Decrement(ref numAsyncOps) == 0)
        {
            // Announce to Main that in fact all thread calls are done.
            asyncOpsAreDone.Set();
        }
    }

    // The callback method's signature MUST match that of a
    // System.Threading.TimerCallback delegate (it takes an Object
    // parameter and returns void).
    static void UnSyncUpdateResource(Object state)
    {
        // This calls the unsynchronized method, passing a thread number.
        UnSyncRes.Access((Int32) state);

        // Count down the number of methods that the threads have called.
        // This must be synchronized, however; you cannot know which thread
        // will access the value **before** another thread's incremented
        // value has been stored into the variable.
        if (Interlocked.Decrement(ref numAsyncOps) == 0)
        {
            // Announce to Main that in fact all thread calls are done.
            asyncOpsAreDone.Set();
        }
    }
}
#using <System.dll>

using namespace System;
using namespace System::Threading;

// Note: The class whose internal public member is the synchronizing
// method is not public; none of the client code takes a lock on the
// Resource object.The member of the nonpublic class takes the lock on
// itself. Written this way, malicious code cannot take a lock on
// a public object.
ref class SyncResource
{
public:
    void Access(Int32 threadNum)
    {
        // Uses Monitor class to enforce synchronization.
        Monitor::Enter(this);
        try
        {
            // Synchronized: Despite the next conditional, each thread
            // waits on its predecessor.
            if (threadNum % 2 == 0)
            {
                Thread::Sleep(2000);
            }
            Console::WriteLine("Start Synched Resource access (Thread={0})", threadNum);
            Thread::Sleep(200);
            Console::WriteLine("Stop Synched Resource access  (Thread={0})", threadNum);
        }
        finally
        {
            Monitor::Exit(this);
        }
    }
};

// Without the lock, the method is called in the order in which threads reach it.
ref class UnSyncResource
{
public:
    void Access(Int32 threadNum)
    {
        // Does not use Monitor class to enforce synchronization.
        // The next call throws the thread order.
        if (threadNum % 2 == 0)
        {
            Thread::Sleep(2000);
        }
        Console::WriteLine("Start UnSynched Resource access (Thread={0})", threadNum);
        Thread::Sleep(200);
        Console::WriteLine("Stop UnSynched Resource access  (Thread={0})", threadNum);
    }
};

public ref class App
{
private:
    static Int32 numAsyncOps = 5;
    static AutoResetEvent^ asyncOpsAreDone = gcnew AutoResetEvent(false);
    static SyncResource^ SyncRes = gcnew SyncResource();
    static UnSyncResource^ UnSyncRes = gcnew UnSyncResource();

public:
    static void Main()
    {
        for (Int32 threadNum = 0; threadNum < 5; threadNum++)
        {
            ThreadPool::QueueUserWorkItem(gcnew WaitCallback(SyncUpdateResource), threadNum);
        }

        // Wait until this WaitHandle is signaled.
        asyncOpsAreDone->WaitOne();
        Console::WriteLine("\t\nAll synchronized operations have completed.\t\n");

        // Reset the thread count for unsynchronized calls.
        numAsyncOps = 5;

        for (Int32 threadNum = 0; threadNum < 5; threadNum++)
        {
            ThreadPool::QueueUserWorkItem(gcnew WaitCallback(UnSyncUpdateResource), threadNum);
        }

        // Wait until this WaitHandle is signaled.
        asyncOpsAreDone->WaitOne();
        Console::WriteLine("\t\nAll unsynchronized thread operations have completed.");
    }

    // The callback method's signature MUST match that of a
    // System.Threading.TimerCallback delegate (it takes an Object
    // parameter and returns void).
    static void SyncUpdateResource(Object^ state)
    {
        // This calls the internal synchronized method, passing
        // a thread number.
        SyncRes->Access((Int32) state);

        // Count down the number of methods that the threads have called.
        // This must be synchronized, however; you cannot know which thread
        // will access the value **before** another thread's incremented
        // value has been stored into the variable.
        if (Interlocked::Decrement(numAsyncOps) == 0)
        {
            // Announce to Main that in fact all thread calls are done.
            asyncOpsAreDone->Set();
        }
    }

    // The callback method's signature MUST match that of a
    // System.Threading.TimerCallback delegate (it takes an Object
    // parameter and returns void).
    static void UnSyncUpdateResource(Object^ state)
    {
        // This calls the unsynchronized method, passing a thread number.
        UnSyncRes->Access((Int32) state);

        // Count down the number of methods that the threads have called.
        // This must be synchronized, however; you cannot know which thread
        // will access the value **before** another thread's incremented
        // value has been stored into the variable.
        if (Interlocked::Decrement(numAsyncOps) == 0)
        {
            // Announce to Main that in fact all thread calls are done.
            asyncOpsAreDone->Set();
        }
    }
};

int main()
{
    App::Main();
}

Siehe auch

Referenz

Monitor

Weitere Ressourcen

Threadingobjekte und -features