Monitor Klasse
Definition
Wichtig
Einige Informationen beziehen sich auf Vorabversionen, die vor dem Release ggf. grundlegend überarbeitet werden. Microsoft übernimmt hinsichtlich der hier bereitgestellten Informationen keine Gewährleistungen, seien sie ausdrücklich oder konkludent.
Stellt einen Mechanismus bereit, der den Zugriff auf Objekte synchronisiert.
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
- Vererbung
-
Monitor
- Attribute
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 asynchron auf einem Threadpoolthread ausgeführt werden. Jede Aufgabe generiert 10.000 Zufällige Zahlen, berechnet ihren Mittelwert und aktualisiert zwei Prozedurebenenvariablen, die die Anzahl der generierten Zufallszahlen und deren Summe beibehalten. Nachdem alle Vorgänge ausgeführt wurden, werden diese beiden Werte dann zum Berechnen des Gesamtwerts verwendet.
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 Example
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 sie von jeder Aufgabe, auf die auf einem Threadpoolthread ausgeführt wird, zugegriffen werden kann, müssen sie auch auf die Variablen total zugreifen und n auch synchronisiert werden. Die Interlocked.Add Methode wird für diesen Zweck verwendet.
Im folgenden Beispiel wird die kombinierte Verwendung der Monitor Klasse (implementiert mit dem lock SyncLock Konstrukt der Sprache), der Interlocked Klasse und der AutoResetEvent Klasse veranschaulicht. 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 SyncUpdateResourceMethode 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 es 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 dekrementieren, da Andernfalls nicht sicher sein kann, dass ein zweiter Thread auf den Wert zugreifen wird, bevor der decrementierte Wert eines ersten Threads in der Variable gespeichert wurde. Wenn der letzte synchronisierte Arbeitsthread den Zähler auf Null reduziert, der angibt, dass alle synchronisierten Threads den Zugriff auf die Ressource abgeschlossen haben, ruft die SyncUpdateResource Methode die Methode auf, die den EventWaitHandle.Set Hauptthread angibt, um 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 zugreifen kann, bevor der decrementierte Wert eines ersten Threads der Variable zugewiesen wurde. Wenn der letzte nicht synchronisierte Arbeitsthread den Zähler auf Null dekrementiert, der angibt, dass keine nicht synchronisierten Threads mehr auf die Ressource zugreifen müssen, ruft die UnSyncUpdateResource Methode die EventWaitHandle.Set Methode auf, die die Hauptthreadausführung signalisiert.
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.
Hinweise
Mit der Monitor Klasse können Sie den Zugriff auf einen Codebereich synchronisieren, indem Sie eine Sperre für ein bestimmtes Objekt ausführen und Monitor.Exit freigeben, indem Sie die Methoden und Methoden Monitor.EnterMonitor.TryEnteraufrufen. Objektsperren bieten die Möglichkeit, den Zugriff auf einen Codeblock einzuschränken, häufig als kritischer Abschnitt bezeichnet. Während ein Thread die Sperre für ein Objekt besitzt, kann kein anderer Thread diese Sperre erwerben. Sie können auch die Monitor Klasse 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 mithilfe eines anderen gesperrten Objekts aus.
In diesem Artikel:
Die Monitor-Klasse: Eine Übersicht
Das Sperrobjekt
Der kritische Abschnitt
Pulse, PulseAll und Warten
Überwachen und Warten von Ziehpunkten
Die Monitor-Klasse: Eine Übersicht
Monitor bietet die folgenden Features:
Es ist einem Objekt nach Bedarf zugeordnet.
Es ist ungebunden, was bedeutet, dass er direkt aus jedem Kontext aufgerufen werden kann.
Eine Instanz der Klasse kann nicht erstellt werden; die Methoden Monitor der Monitor Klasse sind alle statisch. Jede Methode wird 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 (das heißt, Referenztypen außer String), nicht 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:
| Aktion | BESCHREIBUNG |
|---|---|
| Enter, TryEnter | Erhält 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, 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. Pulse-Signale werden verwendet, um wartende Threads über Änderungen an dem Zustand eines Objekts zu benachrichtigen. |
| Pulse (Signal), PulseAll | Sendet ein Signal an einen oder mehrere wartende Threads. Das Signal benachrichtigt einen wartenden Thread, dass sich der Zustand des gesperrten Objekts geändert hat, und der Besitzer der Sperre ist bereit, die Sperre zu freigeben. Der wartende Thread wird in der bereiten Warteschlange des Objekts platziert, sodass es möglicherweise die Sperre für das Objekt erhält. Sobald der Thread über die Sperre verfügt, kann er den neuen Zustand des Objekts überprüfen, um zu sehen, ob der erforderliche Zustand erreicht wurde. |
| Exit | Gibt die Sperrung eines Objekts ab. Diese Aktion markiert auch das Ende eines kritischen Abschnitts, der durch das gesperrte Objekt geschützt ist. |
Ab dem .NET Framework 4 gibt es 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 erworben wird, auch wenn eine Ausnahme beim Abrufen der Sperre ausgelöst wird. Verwenden Sie diese Überladungen, wenn es wichtig ist, die Sperrung in allen Fällen zu freigeben, auch wenn die Ressourcen, die die Sperre schützen, möglicherweise nicht in einem konsistenten Zustand sind.
Das Sperrobjekt
Die Monitor-Klasse besteht aus static (in C#) oder Shared (in Visual Basic)-Methoden, die auf einem Objekt ausgeführt werden, das den Zugriff auf den kritischen Abschnitt steuert. Die folgenden Informationen werden für jedes synchronisierte Objekt beibehalten:
Ein Verweis auf den Thread, der derzeit die Sperre enthält.
Ein Verweis auf eine bereite Warteschlange, die die Threads enthält, die bereit sind, die Sperre abzurufen.
Ein Verweis auf eine wartezeitende Warteschlange, 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 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 Example
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 Example
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, auf dem synchronisiert werden soll, sollten Sie nur private oder interne Objekte sperren. Die Sperrung externer Objekte kann zu Deadlocks führen, da nicht verknüpfter Code die gleichen Objekte auswählen kann, die für verschiedene Zwecke gesperrt werden sollen.
Beachten Sie, dass Sie auf einem Objekt in mehreren Anwendungsdomänen synchronisieren können, wenn das für die Sperrung verwendete Objekt von MarshalByRefObject.
Der kritische Abschnitt
Verwenden Sie die Enter Exit Methoden, um den Anfang und das Ende eines kritischen Abschnitts zu markieren.
Hinweis
Die von den Enter Methoden bereitgestellte Funktionalität ist identisch mit der von der Lock-Anweisung in C# und Exit der SyncLock-Anweisung in Visual Basic bereitgestellten Funktionen, außer dass die Sprache 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 erworbene Sperrung, dass nur ein einzelner Thread den eingeschlossenen Code mit dem gesperrten Objekt ausführen kann. In diesem Fall wird empfohlen, diesen Code in einem Block zu platzieren und den Aufruf der Exit Methode in einem try 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 ein kritischer Abschnitt eine gesamte Methode umfasst, kann die Sperranlage erreicht werden, indem System.Runtime.CompilerServices.MethodImplAttribute Sie die Methode platzieren und den Synchronized Wert im Konstruktor System.Runtime.CompilerServices.MethodImplAttributeangeben. Wenn Sie dieses Attribut verwenden, sind die Aufrufe Enter 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 hält, bis die Methode zurückgegeben wird; wenn die Sperre früher veröffentlicht werden kann, verwenden Sie die Klasse, die Monitor C#- Sperr-Anweisung oder die Visual Basic SyncLock-Anweisung innerhalb der Methode anstelle des Attributs.
Obwohl es möglich ist, dass die Enter Exit Sperre und Veröffentlichung eines bestimmten Objekts für Member- oder Klassengrenzen oder beides möglich ist, wird diese Praxis nicht empfohlen.
Pulse, PulseAll und Warten
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, wird die Sperre freigegeben, und der Thread wird der Wartezeitwarteschlange des synchronisierten Objekts hinzugefügt. Der erste Thread in der bereiten Warteschlange, falls vorhanden, erhält die Sperre und gibt den kritischen Abschnitt ein. Der Thread, Wait der aufgerufen wurde, wird von der Wartenwarteschlange in die bereite Warteschlange verschoben, wenn entweder Monitor.Pulse Monitor.PulseAll die Methode oder die Methode vom Thread aufgerufen wird, der die Sperre enthält (um verschoben zu werden, muss der Thread an der Spitze der Wartenwarteschlange sein). Die Wait Methode gibt zurück, wenn der aufrufende Thread die Sperrung erneut erzwingt.
Wenn der Thread, der die Sperraufrufe Pulseenthält, wird der Thread an der Kopfzeile der Wartezeit in die fertige Warteschlange verschoben. Der Aufruf der Methode verschiebt alle Threads aus der PulseAll Wartezeit in die bereite Warteschlange.
Überwachen und Warten von Ziehpunkten
Es ist wichtig, den Unterschied zwischen der Verwendung der Monitor Klasse und WaitHandle objekte zu beachten.
Die Monitor Klasse ist rein verwaltet, vollständig portierbar und kann in Bezug auf die Anforderungen der Betriebssystemressourcen 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.
Eigenschaften
| LockContentionCount |
Ruft die Anzahl der Konflikte ab beim Versuch, die Sperre des Monitors aufzuheben. |
Methoden
| Enter(Object) |
Erhält eine exklusive Sperre für das angegebene Objekt. |
| Enter(Object, Boolean) |
Erhält eine exklusive Sperre für das angegebene Objekt und legt atomar einen Wert fest, der angibt, ob die Sperre angenommen wurde. |
| Exit(Object) |
Hebt eine exklusive Sperre für das angegebene Objekt auf. |
| IsEntered(Object) |
Bestimmt, ob der aktuelle Thread die Sperre für das angegebene Objekt enthält. |
| Pulse(Object) |
Benachrichtigt einen Thread in der Warteschlange für abzuarbeitende Threads über eine Änderung am Zustand des gesperrten Objekts. |
| PulseAll(Object) |
Benachrichtigt alle wartenden Threads über eine Änderung am Zustand des Objekts. |
| TryEnter(Object) |
Versucht, eine exklusive Sperre für das angegebene Objekt zu erhalten. |
| TryEnter(Object, Boolean) |
Versucht, eine exklusive Sperre für das angegebene Objekt zu erhalten, und legt atomar einen Wert fest, der angibt, ob die Sperre angenommen wurde. |
| TryEnter(Object, Int32) |
Versucht über eine angegebene Anzahl von Millisekunden hinweg, eine exklusive Sperre für das angegebene Objekt zu erhalten. |
| TryEnter(Object, Int32, Boolean) |
Versucht für die angegebene Anzahl von Millisekunden, eine exklusive Sperre für das angegebene Objekt zu erhalten, und legt atomar einen Wert fest, der angibt, ob die Sperre angenommen wurde. |
| TryEnter(Object, TimeSpan) |
Versucht über einen angegebenen Zeitraum hinweg, eine exklusive Sperre für das angegebene Objekt zu erhalten. |
| TryEnter(Object, TimeSpan, Boolean) |
Versucht für die angegebene Dauer, eine exklusive Sperre für das angegebene Objekt zu erhalten, und legt atomar einen Wert fest, der angibt, ob die Sperre angenommen wurde. |
| Wait(Object) |
Hebt die Sperre für ein Objekt auf und blockiert den aktuellen Thread, bis er die Sperre erneut erhält. |
| Wait(Object, Int32) |
Hebt die Sperre für ein Objekt auf und blockiert den aktuellen Thread, bis er die Sperre erneut erhält. Wenn das angegebene Timeoutintervall abläuft, tritt der Thread in die Warteschlange für abgearbeitete Threads ein. |
| Wait(Object, Int32, Boolean) |
Hebt die Sperre für ein Objekt auf und blockiert den aktuellen Thread, bis er die Sperre erneut erhält. Wenn das angegebene Timeoutintervall abläuft, tritt der Thread in die Warteschlange für abgearbeitete Threads ein. Diese Methode gibt außerdem an, ob die Synchronisierungsdomäne danach für den Kontext (wenn es sich um einen synchronisierten Kontext handelt) vor dem Warten und erneuten Erhalten verlassen wird. |
| Wait(Object, TimeSpan) |
Hebt die Sperre für ein Objekt auf und blockiert den aktuellen Thread, bis er die Sperre erneut erhält. Wenn das angegebene Timeoutintervall abläuft, tritt der Thread in die Warteschlange für abgearbeitete Threads ein. |
| Wait(Object, TimeSpan, Boolean) |
Hebt die Sperre für ein Objekt auf und blockiert den aktuellen Thread, bis er die Sperre erneut erhält. Wenn das angegebene Timeoutintervall abläuft, tritt der Thread in die Warteschlange für abgearbeitete Threads ein. Beendet vor dem Warten optional auch die Synchronisierungsdomäne für den synchronisierten Kontext und erhält die Domäne anschließend wieder. |
Gilt für:
Threadsicherheit
Dieser Typ ist threadsicher.