System.Threading.Monitor-Klasse
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 verwenden und freigeben, indem Sie die Monitor.Enter, Monitor.TryEnterund 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 ist einem Objekt bei Bedarf 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 Monitor Klasse, um andere Objekte als Zeichenfolgen zu sperren (d. h. Verweistypen außer StringWerttypen). 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:
Aktion | 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 für ein Objekt frei, um anderen Threads das Sperren und Zugreifen auf das Objekt zu ermöglichen. 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 Wartethread wird in der bereiten Warteschlange des Objekts platziert, sodass er schließlich die Sperre für das Objekt empfängt. 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 Überladungen für die Enter und TryEnter Methoden. Eine Reihe von Überladungen weist einen ref
(in C#) oder ByRef
(in Visual Basic) Boolean Parameter auf, der atomisch festgelegt true
ist, wenn die Sperre abgerufen wird, auch wenn beim Abrufen der Sperre eine Ausnahme ausgelöst wird. Verwenden Sie diese Überladungen, wenn es wichtig ist, die Sperre in allen Fällen freizugeben, auch wenn sich die zu schützenden Ressourcen möglicherweise nicht in einem konsistenten Zustand 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 Standard beibehalten:
- Ein Verweis auf den Thread, der derzeit die Sperre enthält.
- Ein Verweis auf eine bereite Warteschlange, die die Threads enthält, die zum Abrufen der Sperre bereit sind.
- 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. Referenztypen), nicht 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, bedingt Enter nie eine Blockierung, und der Code, den die Methode eigentlich schützen 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.“
Dieses Problem wird anhand des folgenden Beispiels veranschaulicht. Im Beispiel werden zehn Aufgaben gestartet, wobei jede lediglich 250 Millisekunden inaktiv ist. Anschließend aktualisiert jede Aufgabe eine Zählervariable, nTasks
, die dazu vorgesehen ist, die Anzahl von Aufgaben zu zählen, die tatsächlich gestartet wurden und ausgeführt werden. Weil nTasks
eine globale Variable ist, die von mehreren Aufgaben gleichzeitig aktualisiert werden kann, wird ein Monitor verwendet, um zu verhindern, dass die Variable gleichzeitig von mehreren Aufgaben geändert wird. 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("{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.
Jede Aufgabe löst eine SynchronizationLockException-Ausnahme aus, weil die Variable nTasks
gekapselt wird, bevor in jeder Aufgabe die Monitor.Enter-Methode aufgerufen wird. Anders formuliert heißt das, bei jedem Methodenaufruf wird eine separate Variable übergeben, die unabhängig von den anderen ist. nTasks
wird im Aufruf der Monitor.Exit-Methode erneut gekapselt. Dadurch werden wiederum zehn neue gekapselte nTasks
-Variablen erstellt, die unabhängig voneinander sind. Hinzu kommen die zehn gekapselten Variablen, die im Aufruf der Monitor.Enter-Methode erstellt wurden. Die Ausnahme wird dann ausgelöst, weil im Code versucht wird, eine Sperre für eine neu erstellte Variable freizugeben, die zuvor nicht gesperrt wurde.
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 nicht gekapselten Variablen wirken sich nicht auf die gekapselte Kopie aus, und es ist nicht möglich, den Wert der gekapselten 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("{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.
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 für ein Objekt in mehreren Anwendungen synchronisieren können Standard s, wenn das für die Sperre verwendete Objekt von MarshalByRefObject.
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 den Methoden bereitgestellte Funktionalität ist identisch mit der von der Lock-Anweisung in C# und der SyncLock-Anweisung in Visual Basic bereitgestellten, mit der Ausnahme, dass die Sprachkonstrukte die Monitor.Enter(Object, Boolean) Methodenüberladung und die Monitor.Exit Methode in einer try
...finally
blockieren, 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 ist sichergestellt, dass die Sperre aufgehoben wird, selbst 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 gesamte Methode erstreckt, kann die Sperranlage durch Platzieren der System.Runtime.CompilerServices.MethodImplAttribute Methode und Angeben des Synchronized Werts im Konstruktor von System.Runtime.CompilerServices.MethodImplAttributeerreicht werden. Wenn Sie dieses Attribut verwenden, sind die Enter Aufrufe und Exit Methodenaufrufe 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 gedrückt hält, bis die Methode zurückgegeben wird. wenn die Sperre früher veröffentlicht werden kann, verwenden Sie die Monitor Klasse, die C# -Lock-Anweisung oder die Visual Basic SyncLock-Anweisung innerhalb der Methode anstelle des Attributs.
Obwohl es möglich ist, dass die EnterExit Sperre und Freigabe eines bestimmten Objekts über Member- oder Klassengrenzen hinweg oder beides erfolgen kann, wird diese Vorgehensweise 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 die Sperraufrufe Waitenthält, freigegeben wird und der Thread der Wartewarteschlange des synchronisierten Objekts hinzugefügt wird. Der erste Thread in der bereiten Warteschlange erhält ggf. die Sperre und wechselt in den kritischen Abschnitt. Der aufgerufene Wait Thread wird von der Wartewarteschlange in die bereite Warteschlange verschoben, wenn entweder die Monitor.Pulse oder die Monitor.PulseAll Methode vom Thread aufgerufen wird, der die Sperre enthält (um verschoben zu werden, muss der Thread an der Spitze der Wartewarteschlange sein). Die Wait Methode gibt zurück, wenn der aufrufende Thread die Sperre erneut aufruft.
Wenn der Thread, der die Sperraufrufe Pulseenthält, wird der Thread an der Kopfzeile der Warteschleife in die bereite Warteschlange verschoben. Der Aufruf der PulseAll Methode verschiebt alle Threads aus der Warteschleife in die fertige Warteschlange.
Monitore und Wartepunkte
Es ist wichtig, die Unterscheidung zwischen der Verwendung der Klasse und WaitHandle der Monitor 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 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 machen einige höhere Betriebssystemfunktionen verfügbar, z. B. die Fähigkeit, 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 Threadpoolthread ausgeführt werden. Jeder Vorgang generiert 10.000 Zufallszahlen, berechnet den Mittelwert und aktualisiert zwei Variablen auf Prozedurebene, die eine laufende Summe der generierten Zufallszahlen und deren Summe Standard. 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: {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)
Da auf sie über jede Aufgabe zugegriffen werden kann, die in einem Threadpoolthread ausgeführt wird, müssen Sie auf die Variablen total
zugreifen und n
auch 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. Im Beispiel werden zwei internal
- (in C#) oder Friend
-Klassen (in Visual Basic), SyncResource
und UnSyncResource
, definiert, die entsprechend synchronisierten und nicht synchronisierten Zugriff auf eine Ressource bereitstellen. Damit sichergestellt ist, dass der Unterschied zwischen dem synchronisierten und dem nicht synchronisierten Zugriff im Beispiel deutlich wird (was der Fall sein kann, wenn jeder Methodenaufruf schnell abgeschlossen wird), enthält die Methode eine zufällige Verzögerung: Für Threads, deren Thread.ManagedThreadId-Eigenschaft einen geraden Wert hat, ruft die Methode die Thread.Sleep-Methode auf, um eine Verzögerung von 2.000 Millisekunden einzuführen. Weil die SyncResource
-Klasse nicht öffentlich ist, löst keiner der Clientcodes eine Sperre für die synchronisierte Ressource aus, sondern die Sperre wird von der internen Klasse selbst ausgelöst. 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 #{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.
Im Beispiel wird eine Variable, numOps
, definiert, mit der die Anzahl von Threads festgelegt wird, die versuchen, auf die Ressource zuzugreifen. Der Anwendungsthread ruft die ThreadPool.QueueUserWorkItem(WaitCallback)-Methode jeweils fünf Mal für synchronisierten und nicht synchronisierten Zugriff auf. Die ThreadPool.QueueUserWorkItem(WaitCallback)-Methode hat einen einzigen Parameter: ein Delegat, der keine Parameter akzeptiert und keinen Wert zurückgibt. Für synchronisierten Zugriff und ruft die Methode die SyncUpdateResource
Methode auf, für nicht synchronisierten Zugriff ruft sie die UnSyncUpdateResource
-Methode auf. Nach jedem Satz von Methodenaufrufen ruft der Anwendungsthread die AutoResetEvent.WaitOne-Methode auf, sodass sie blockiert wird, bis die AutoResetEvent Instanz signalisiert wird.
Bei jedem Aufruf der SyncUpdateResource
-Methode wird die interne SyncResource.Access
-Methode und dann die Interlocked.Decrement-Methode aufgerufen, um den numOps
-Zähler zu dekrementieren. Die Interlocked.Decrement Methode wird verwendet, um den Zähler zu erhöhen, da Andernfalls 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 Standard Thread signalisiert, die Ausführung fortzusetzen.
Bei jedem Aufruf der UnSyncUpdateResource
-Methode wird die interne UnSyncResource.Access
-Methode und dann die Interlocked.Decrement-Methode aufgerufen, um den numOps
-Zähler zu dekrementieren. Erneut wird die Interlocked.Decrement Methode verwendet, um den Zähler zu dekrementieren, um sicherzustellen, dass ein zweiter Thread nicht auf den Wert zugreift, bevor der Variablen der Wert eines ersten Threads dekrementiert wurde. Wenn der letzte nicht synchronisierte Workerthread den Zähler auf Null erhöht, was angibt, dass keine nicht synchronisierten Threads mehr auf die Ressource zugreifen müssen, ruft die UnSyncUpdateResource
Methode die EventWaitHandle.Set Methode auf, die den Standard Thread signalisiert, die Ausführung fortzusetzen.
Wie die Ausgabe des Beispiels zeigt, wird mit synchronisiertem Zugriff sichergestellt, dass der aufrufende Thread die geschützte Ressource beendet, bevor ein anderer Thread auf sie zugreifen kann. Jeder Thread wartet auf seinen Vorgänger. Andererseits, ohne die Sperre, wird die UnSyncResource.Access
-Methode in der Reihenfolge aufgerufen, in der sie von Threads erreicht wird.