Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Dieser Artikel enthält ergänzende Hinweise zur Referenzdokumentation für diese API.
Mit der Monitor Klasse können Sie den Zugriff auf einen Codebereich synchronisieren, indem Sie eine Sperre für ein bestimmtes Objekt übernehmen und freigeben, indem Sie die Monitor.Enter, Monitor.TryEnter und Monitor.Exit Methoden aufrufen. Objektsperren bieten die Möglichkeit, den Zugriff auf einen Codeblock einzuschränken, der häufig als kritischer Abschnitt bezeichnet wird. Während ein Thread die Sperre für ein Objekt besitzt, kann kein anderer Thread diese Sperre abrufen. Sie können die Monitor Klasse auch verwenden, um sicherzustellen, dass kein anderer Thread auf einen Abschnitt des Anwendungscodes zugreifen darf, der vom Sperrbesitzer ausgeführt wird, es sei denn, der andere Thread führt den Code mit einem anderen gesperrten Objekt aus. Da die Monitor-Klasse threadaffin ist, muss der Thread, der eine Sperre erworben hat, die Sperre freigeben, indem die Monitor.Exit-Methode aufgerufen wird.
Überblick
Monitor verfügt über die folgenden Features:
- Es wird bei Bedarf einem Objekt zugeordnet.
- Sie ist ungebunden, was bedeutet, dass sie direkt aus jedem Kontext aufgerufen werden kann.
- Eine Instanz der Monitor Klasse kann nicht erstellt werden. Die Methoden der Monitor Klasse sind alle statisch. Jede Methode wird an das synchronisierte Objekt übergeben, das den Zugriff auf den kritischen Abschnitt steuert.
Hinweis
Verwenden Sie die Klasse Monitor, um andere Objekte als Zeichenfolgen (d.h. andere Referenztypen als String) zu sperren, keine Werttypen. Ausführliche Informationen finden Sie in den Überladungen der Enter Methode und im Abschnitt " Sperrobjekt " weiter unten in diesem Artikel.
In der folgenden Tabelle werden die Aktionen beschrieben, die von Threads ausgeführt werden können, die auf synchronisierte Objekte zugreifen:
Maßnahme | BESCHREIBUNG |
---|---|
Enter, TryEnter | Erwirbt eine Sperre für ein Objekt. Diese Aktion markiert auch den Anfang eines kritischen Abschnitts. Kein anderer Thread kann den kritischen Abschnitt eingeben, es sei denn, er führt die Anweisungen im kritischen Abschnitt mithilfe eines anderen gesperrten Objekts aus. |
Wait | Gibt die Sperre eines Objekts frei, damit andere Threads das Objekt sperren und darauf zugreifen können. Der aufrufende Thread wartet, während ein anderer Thread auf das Objekt zugreift. Impulssignale werden verwendet, um Wartethreads über Änderungen am Zustand eines Objekts zu benachrichtigen. |
Pulse (Signal), PulseAll | Sendet ein Signal an einen oder mehrere Wartethreads. Das Signal benachrichtigt einen Wartethread, dass sich der Zustand des gesperrten Objekts geändert hat, und der Besitzer der Sperre ist bereit, die Sperre freizugeben. Der wartende Thread wird in die Warteschlange des Objekts gestellt, sodass er schließlich das Lock für das Objekt erhalten kann. Sobald der Thread über die Sperre verfügt, kann er den neuen Zustand des Objekts überprüfen, um festzustellen, ob der erforderliche Zustand erreicht wurde. |
Exit | Gibt die Sperre für ein Objekt frei. Diese Aktion markiert auch das Ende eines kritischen Abschnitts, der durch das gesperrte Objekt geschützt ist. |
Es gibt zwei Sätze von Überladungen für die Enter- und TryEnter-Methoden. Ein Set von Überladungen hat einen ref
(in C#) oder ByRef
(in Visual Basic) Boolean Parameter, der atomar auf true
festgelegt wird, wenn das Lock erworben wird, auch wenn beim Erwerb des Locks eine Ausnahme ausgelöst wird. Verwenden Sie diese Überladungen, wenn es wichtig ist, das Lock in jedem Fall freizugeben, auch wenn sich die Ressourcen, die das Lock schützt, möglicherweise nicht in einem konsistenten Status befinden.
Das Sperrobjekt
Die Monitor-Klasse besteht aus static
(Shared
in Visual Basic) Methoden, die für ein Objekt arbeiten, das den Zugriff auf den kritischen Abschnitt steuert. Die folgenden Informationen werden für jedes synchronisierte Objekt verwaltet:
- Ein Verweis auf den Thread, der derzeit die Sperre enthält.
- Ein Verweis auf eine Warteschlange, die die Threads enthält, die bereit sind, das Lock abzurufen.
- Ein Verweis auf eine Warteschleife, die die Threads enthält, die auf eine Benachrichtigung über eine Änderung im Zustand des gesperrten Objekts warten.
Monitor Sperrt Objekte (d. h. Verweistypen) und keine Werttypen. Sie können einen Werttyp an Enter und Exit übergeben. Dieser wird jedoch für jeden Aufruf separat geschachtelt. Da jeder Aufruf ein separates Objekt erstellt, blockiert Enter tatsächlich nie und der Code, den es angeblich schützen soll, wird nicht wirklich synchronisiert. Darüber hinaus unterscheidet sich das an Exit übergebene Objekt von dem an Enter übergebenen Objekt, sodass Monitor eine SynchronizationLockException-Ausnahme mit der Meldung "Objektsynchronisierungsmethode wurde von einem nicht synchronisierten Codeblock aufgerufen" auslöst.
Das folgende Beispiel veranschaulicht dieses Problem. Es startet zehn Aufgaben, von denen jede nur 250 Millisekunden ruht. Jeder Vorgang aktualisiert dann eine Zählervariable, nTasks
die die Anzahl der Vorgänge zählen soll, die tatsächlich gestartet und ausgeführt wurden. Da nTasks
es sich um eine globale Variable handelt, die von mehreren Aufgaben gleichzeitig aktualisiert werden kann, wird ein Monitor verwendet, um ihn vor gleichzeitiger Änderung durch mehrere Aufgaben zu schützen. Wie die Ausgabe des Beispiels zeigt, löst jede dieser Aufgaben jedoch eine SynchronizationLockException-Ausnahme aus.
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.
Jede Aufgabe löst eine SynchronizationLockException-Ausnahme aus, weil die Variable nTasks
gekapselt wird, bevor in jeder Aufgabe die Monitor.Enter-Methode aufgerufen wird. Mit anderen Worten, jeder Methodenaufruf wird eine separate Variable übergeben, die unabhängig von den anderen ist.
nTasks
wird beim Aufruf der Methode Monitor.Exit wieder geboxed. Dies erstellt erneut zehn neue Boxvariablen, die unabhängig voneinander sind, nTasks
und die zehn boxierten Variablen, die im Aufruf der Monitor.Enter Methode erstellt wurden. Die Ausnahme wird ausgelöst, da unser Code versucht, eine Sperre für eine neu erstellte Variable freizugeben, die zuvor nicht gesperrt war.
Wie das folgende Beispiel zeigt, können Sie eine Werttypvariable zwar vor dem Aufruf von Enter und Exit kapseln und dasselbe gekapselte Objekt an beide Methoden übergeben, diese Vorgehensweise bietet jedoch keinerlei Vorteile. Änderungen an der unboxierten Variablen werden nicht in der boxierten Kopie widergespiegelt, und es gibt keine Möglichkeit, den Wert der boxierten Kopie zu ändern.
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.
Wenn Sie ein Objekt auswählen, für das die Synchronisierung ausgeführt werden soll, sollten Sie nur private oder interne Objekte sperren. Das Sperren externer Objekte kann zu Deadlocks führen, da nicht verknüpfter Code die gleichen Objekte auswählen kann, die für unterschiedliche Zwecke gesperrt werden sollen.
Beachten Sie, dass Sie ein Objekt in mehreren App-Domänen synchronisieren können, wenn das für das Lock verwendete Objekt von MarshalByRefObject abgeleitet ist.
Der kritische Abschnitt
Verwenden Sie die Enter Methoden, Exit um den Anfang und das Ende eines kritischen Abschnitts zu markieren.
Hinweis
Die von den Enter- und Exit-Methoden bereitgestellte Funktionalität ist identisch mit der von der lock-Anweisung in C# und der SyncLock-Anweisung in Visual Basic, außer dass die Sprachkonstrukte die Monitor.Enter(Object, Boolean)-Methodenüberladung und die Monitor.Exit-Methode in einen try
...
finally
-Block, um sicherzustellen, dass der Monitor freigegeben wird.
Wenn der kritische Abschnitt eine Reihe zusammenhängender Anweisungen ist, garantiert die von der Enter Methode abgerufene Sperre, dass nur ein einzelner Thread den eingeschlossenen Code mit dem gesperrten Objekt ausführen kann. In diesem Fall wird empfohlen, diesen Code in einem try
Block zu platzieren und den Aufruf der Exit Methode in einem finally
Block zu platzieren. Dadurch wird sichergestellt, dass die Sperre auch dann freigegeben wird, wenn eine Ausnahme auftritt. Das folgende Codefragment veranschaulicht dieses Muster.
// 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
Diese Einrichtung wird in der Regel verwendet, um den Zugriff auf eine statische oder Instanzmethode einer Klasse zu synchronisieren.
Wenn sich ein kritischer Abschnitt über eine ganze Methode erstreckt, kann das Locking erreicht werden, indem das System.Runtime.CompilerServices.MethodImplAttribute auf die Methode platziert und der Wert Synchronized im Konstruktor von System.Runtime.CompilerServices.MethodImplAttribute angegeben wird. Wenn Sie dieses Attribut verwenden, sind die Methodenaufrufe Enter und Exit nicht erforderlich. Das folgende Codefragment veranschaulicht dieses Muster:
[MethodImplAttribute(MethodImplOptions.Synchronized)]
void MethodToLock()
{
// Method implementation.
}
<MethodImplAttribute(MethodImplOptions.Synchronized)>
Sub MethodToLock()
' Method implementation.
End Sub
Beachten Sie, dass das Attribut bewirkt, dass der aktuelle Thread die Sperre behält, bis die Methode zurückkehrt. Wenn die Sperre früher freigegeben werden kann, verwenden Sie die Monitor-Klasse, die C#-lock-Anweisung oder die Visual Basic-SyncLock-Anweisung innerhalb der Methode anstelle des Attributs.
Es ist zwar möglich, dass die Enter- und Exit-Anweisungen, die ein bestimmtes Objekt sperren und freigeben, Mitglieder- oder Klassengrenzen oder beides überschreiten, aber diese Praxis wird nicht empfohlen.
Pulse, PulseAll, und Wait
Sobald ein Thread die Sperre besitzt und den kritischen Abschnitt eingegeben hat, den die Sperre schützt, kann er die Monitor.Wait, Monitor.Pulseund Monitor.PulseAll Methoden aufrufen.
Wenn der Thread, der das Lock besitzt, Wait aufruft, wird das Lock freigegeben und der Thread wird in die Warteschlange des synchronisierten Objekts aufgenommen. Der erste Thread in der Warteschlange, sofern vorhanden, bezieht das Lock und betritt den kritischen Abschnitt. Der Wait-Thread wird von der Warteschlange in die Bereitwarteschlange verschoben, wenn entweder die Monitor.Pulse- oder die Monitor.PulseAll-Methode von dem Thread aufgerufen wird, der die Sperre hält (um verschoben zu werden, muss der Thread an der Spitze der Warteschlange sein). Die Methode Wait gibt zurück, wenn der aufrufende Thread das Lock wieder erlangt.
Wenn der Thread, der das Lock hält, Pulse aufruft, wird der Thread, der an der Spitze der Warteschlange steht, in die Readiness-Warteschlange verschoben. Der Aufruf der PulseAll Methode verschiebt alle Threads aus der Warteschleife in die fertige Warteschlange.
Monitore und Wait-Handles
Es ist wichtig, die Unterscheidung zwischen der Verwendung der Monitor Klasse und der WaitHandle Objekte zu beachten.
- Die Monitor Klasse ist rein verwaltet, vollständig portierbar und kann in Bezug auf die Ressourcenanforderungen des Betriebssystems effizienter sein.
- WaitHandle Objekte stellen abwartbare Objekte des Betriebssystems dar, eignen sich für die Synchronisierung zwischen verwaltetem und nicht verwaltetem Code und machen einige erweiterte Betriebssystemfeatures verfügbar, z. B. die Möglichkeit, auf viele Objekte gleichzeitig zu warten.
Beispiele
Im folgenden Beispiel wird die Monitor Klasse verwendet, um den Zugriff auf eine einzelne Instanz eines Zufallszahlengenerators zu synchronisieren, der durch die Random Klasse dargestellt wird. Im Beispiel werden zehn Aufgaben erstellt, die jeweils asynchron in einem Threadpool-Thread ausgeführt werden. Jeder Vorgang generiert 10.000 Zufallszahlen, berechnet den Mittelwert und aktualisiert zwei Variablen auf Prozedurebene, die eine laufende Gesamtanzahl der generierten Zufallszahlen und deren Summe beibehalten. Nachdem alle Vorgänge ausgeführt wurden, werden diese beiden Werte dann zum Berechnen des Gesamtmittelwerts verwendet.
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)
Da auf die Variablen total
und n
über jede Aufgabe zugegriffen werden kann, die in einem Threadpool-Thread ausgeführt wird, muss der Zugriff auf sie ebenfalls synchronisiert werden. Die Interlocked.Add Methode wird zu diesem Zweck verwendet.
Das folgende Beispiel veranschaulicht die kombinierte Verwendung der Monitor Klasse (implementiert mit dem lock
Oder SyncLock
Sprachkonstrukt), der Interlocked Klasse und der AutoResetEvent Klasse. Es definiert zwei internal
Klassen (in C#) oder Friend
(in Visual Basic) SyncResource
und UnSyncResource
, die jeweils synchronisierten und nicht synchronisierten Zugriff auf eine Ressource bereitstellen. Um sicherzustellen, dass das Beispiel den Unterschied zwischen dem synchronisierten und nicht synchronisierten Zugriff veranschaulicht (was der Fall sein könnte, wenn jeder Methodenaufruf schnell abgeschlossen wird), enthält die Methode eine zufällige Verzögerung: Für Threads, deren Thread.ManagedThreadId-Eigenschaft gerade ist, ruft die Methode Thread.Sleep auf, um eine Verzögerung von 2.000 Millisekunden einzuführen. Beachten Sie, dass, da die SyncResource
Klasse nicht öffentlich ist, keiner des Clientcodes eine Sperre für die synchronisierte Ressource akzeptiert; die interne Klasse selbst übernimmt die Sperre. Dadurch wird verhindert, dass bösartiger Code eine Sperre für ein öffentliches Objekt auslöst.
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.
Im Beispiel wird eine Variable definiert, numOps
die die Anzahl der Threads definiert, die versuchen, auf die Ressource zuzugreifen. Der Anwendungsthread ruft die ThreadPool.QueueUserWorkItem(WaitCallback) Methode für synchronisierten und nicht synchronisierten Zugriff fünfmal auf. Die ThreadPool.QueueUserWorkItem(WaitCallback) Methode verfügt über einen einzelnen Parameter, einen Delegaten, der keine Parameter akzeptiert und keinen Wert zurückgibt. Für den synchronisierten Zugriff ruft sie die SyncUpdateResource
Methode auf; für nicht synchronisierten Zugriff wird die UnSyncUpdateResource
Methode aufgerufen. Nach jedem Satz von Methodenaufrufen ruft der Anwendungsthread die AutoResetEvent.WaitOne-Methode auf, sodass sie blockiert wird, bis die AutoResetEvent Instanz signalisiert wird.
Jeder Aufruf der SyncUpdateResource
Methode ruft die interne SyncResource.Access
Methode auf und ruft dann die Interlocked.Decrement Methode auf, um den numOps
Zähler zu verringern. Die Interlocked.Decrement Methode wird verwendet, um den Zähler zu verringern, da man sonst nicht sicher sein kann, dass ein zweiter Thread auf den Wert zugreift, bevor der dekrementierte Wert eines ersten Threads in der Variablen gespeichert wurde. Wenn der letzte synchronisierte Workerthread den Zähler auf Null erhöht, was angibt, dass alle synchronisierten Threads den Zugriff auf die Ressource abgeschlossen haben, ruft die SyncUpdateResource
Methode die EventWaitHandle.Set Methode auf, die den Hauptthread signalisiert, die Ausführung fortzusetzen.
Jeder Aufruf der UnSyncUpdateResource
Methode ruft die interne UnSyncResource.Access
Methode auf und ruft dann die Interlocked.Decrement Methode auf, um den numOps
Zähler zu verringern. Erneut wird die Interlocked.Decrement Methode verwendet, um den Zähler zu dekrementieren und sicherzustellen, dass ein zweiter Thread nicht auf den Wert zugreift, bevor der dekrementierte Wert eines ersten Threads der Variablen zugewiesen wurde. Wenn der letzte nicht synchronisierte Workerthread den Zähler auf Null erhöht, der angibt, dass keine nicht synchronisierten Threads mehr auf die Ressource zugreifen müssen, ruft die UnSyncUpdateResource
Methode die EventWaitHandle.Set Methode auf, die den Hauptthread signalisiert, die Ausführung fortzusetzen.
Wie die Ausgabe aus dem Beispiel zeigt, stellt der synchronisierte Zugriff sicher, dass der aufrufende Thread die geschützte Ressource beendet, bevor ein anderer Thread darauf zugreifen kann; jeder Thread wartet auf seinen Vorgänger. Andererseits wird die Methode UnSyncResource.Access
ohne Sperre in der Reihenfolge aufgerufen, in der Threads sie erreichen.