Monitor Osztály
Definíció
Fontos
Egyes információk olyan, kiadás előtti termékekre vonatkoznak, amelyek a kiadásig még jelentősen módosulhatnak. A Microsoft nem vállal kifejezett vagy törvényi garanciát az itt megjelenő információért.
Olyan mechanizmust biztosít, amely szinkronizálja az objektumokhoz való hozzáférést.
public ref class Monitor abstract sealed
public ref class Monitor sealed
public static class Monitor
public sealed class Monitor
[System.Runtime.InteropServices.ComVisible(true)]
public static class Monitor
type Monitor = class
[<System.Runtime.InteropServices.ComVisible(true)>]
type Monitor = class
Public Class Monitor
Public NotInheritable Class Monitor
- Öröklődés
-
Monitor
- Attribútumok
Példák
Az alábbi példa a Monitor osztály használatával szinkronizálja a hozzáférést a Random osztály által képviselt véletlenszám-generátor egyetlen példányához. A példa tíz feladatot hoz létre, amelyek mindegyike aszinkron módon hajtódik végre egy szálkészletben lévő szálon. Minden tevékenység 10 000 véletlenszerű számot hoz létre, kiszámítja az átlagot, és frissíti a két eljárásszintű változót, amelyek a létrehozott véletlenszerű számok és az összegük futó összegét tartják fenn. Az összes tevékenység végrehajtása után a rendszer ezt a két értéket használja a teljes középérték kiszámításához.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
List<Task> tasks = new List<Task>();
Random rnd = new Random();
long total = 0;
int n = 0;
for (int taskCtr = 0; taskCtr < 10; taskCtr++)
tasks.Add(Task.Run( () => { int[] values = new int[10000];
int taskTotal = 0;
int taskN = 0;
int ctr = 0;
Monitor.Enter(rnd);
// Generate 10,000 random integers
for (ctr = 0; ctr < 10000; ctr++)
values[ctr] = rnd.Next(0, 1001);
Monitor.Exit(rnd);
taskN = ctr;
foreach (var value in values)
taskTotal += value;
Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
Task.CurrentId, (taskTotal * 1.0)/taskN,
taskN);
Interlocked.Add(ref n, taskN);
Interlocked.Add(ref total, taskTotal);
} ));
try {
Task.WaitAll(tasks.ToArray());
Console.WriteLine("\nMean for all tasks: {0:N2} (N={1:N0})",
(total * 1.0)/n, n);
}
catch (AggregateException e) {
foreach (var ie in e.InnerExceptions)
Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message);
}
}
}
// The example displays output like the following:
// Mean for task 1: 499.04 (N=10,000)
// Mean for task 2: 500.42 (N=10,000)
// Mean for task 3: 499.65 (N=10,000)
// Mean for task 8: 502.59 (N=10,000)
// Mean for task 5: 502.75 (N=10,000)
// Mean for task 4: 494.88 (N=10,000)
// Mean for task 7: 499.22 (N=10,000)
// Mean for task 10: 496.45 (N=10,000)
// Mean for task 6: 499.75 (N=10,000)
// Mean for task 9: 502.79 (N=10,000)
//
// Mean for all tasks: 499.75 (N=100,000)
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example4
Public Sub Main()
Dim tasks As New List(Of Task)()
Dim rnd As New Random()
Dim total As Long = 0
Dim n As Integer = 0
For taskCtr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
Dim values(9999) As Integer
Dim taskTotal As Integer = 0
Dim taskN As Integer = 0
Dim ctr As Integer = 0
Monitor.Enter(rnd)
' Generate 10,000 random integers.
For ctr = 0 To 9999
values(ctr) = rnd.Next(0, 1001)
Next
Monitor.Exit(rnd)
taskN = ctr
For Each value In values
taskTotal += value
Next
Console.WriteLine("Mean for task {0,2}: {1:N2} (N={2:N0})",
Task.CurrentId, taskTotal / taskN,
taskN)
Interlocked.Add(n, taskN)
Interlocked.Add(total, taskTotal)
End Sub))
Next
Try
Task.WaitAll(tasks.ToArray())
Console.WriteLine()
Console.WriteLine("Mean for all tasks: {0:N2} (N={1:N0})",
(total * 1.0) / n, n)
Catch e As AggregateException
For Each ie In e.InnerExceptions
Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message)
Next
End Try
End Sub
End Module
' The example displays output like the following:
' Mean for task 1: 499.04 (N=10,000)
' Mean for task 2: 500.42 (N=10,000)
' Mean for task 3: 499.65 (N=10,000)
' Mean for task 8: 502.59 (N=10,000)
' Mean for task 5: 502.75 (N=10,000)
' Mean for task 4: 494.88 (N=10,000)
' Mean for task 7: 499.22 (N=10,000)
' Mean for task 10: 496.45 (N=10,000)
' Mean for task 6: 499.75 (N=10,000)
' Mean for task 9: 502.79 (N=10,000)
'
' Mean for all tasks: 499.75 (N=100,000)
Mivel a szálkészlet-szálon futó bármely tevékenységből elérhetők, a total és n változókhoz való hozzáférést is szinkronizálni kell. A Interlocked.Add metódust erre a célra használják.
Az alábbi példa bemutatja a Monitor osztály (a lock vagy SyncLock nyelvi szerkezettel implementálva), a Interlocked osztály és a AutoResetEvent osztály együttes használatát. Két internal (C#-ban) vagy Friend (Visual Basic)-osztályt határoz meg, SyncResource amelyek UnSyncResourceszinkronizált és nem aszinkron hozzáférést biztosítanak egy erőforráshoz. Annak érdekében, hogy a példa bemutassa a szinkronizált és a nem aszinkron hozzáférés közötti különbséget (ami akkor fordulhat elő, ha az egyes metódushívások gyorsan befejeződnek), a metódus véletlenszerű késleltetést tartalmaz: olyan szálak esetében, amelyek Thread.ManagedThreadId tulajdonsága egyenlő, a metódus 2000 ezredmásodperc késleltetést hív Thread.Sleep meg. Vegye figyelembe, hogy mivel az SyncResource osztály nem nyilvános, egyik ügyfélkód sem zárolja a szinkronizált erőforrást; maga a belső osztály veszi át a zárolást. Ez megakadályozza, hogy a rosszindulatú kód zároljon egy nyilvános objektumot.
using System;
using System.Threading;
internal class SyncResource
{
// Use a monitor to enforce synchronization.
public void Access()
{
lock(this) {
Console.WriteLine("Starting synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId);
if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
Thread.Sleep(2000);
Thread.Sleep(200);
Console.WriteLine("Stopping synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId);
}
}
}
internal class UnSyncResource
{
// Do not enforce synchronization.
public void Access()
{
Console.WriteLine("Starting unsynchronized resource access on Thread #{0}",
Thread.CurrentThread.ManagedThreadId);
if (Thread.CurrentThread.ManagedThreadId % 2 == 0)
Thread.Sleep(2000);
Thread.Sleep(200);
Console.WriteLine("Stopping unsynchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId);
}
}
public class App
{
private static int numOps;
private static AutoResetEvent opsAreDone = new AutoResetEvent(false);
private static SyncResource SyncRes = new SyncResource();
private static UnSyncResource UnSyncRes = new UnSyncResource();
public static void Main()
{
// Set the number of synchronized calls.
numOps = 5;
for (int ctr = 0; ctr <= 4; ctr++)
ThreadPool.QueueUserWorkItem(new WaitCallback(SyncUpdateResource));
// Wait until this WaitHandle is signaled.
opsAreDone.WaitOne();
Console.WriteLine("\t\nAll synchronized operations have completed.\n");
// Reset the count for unsynchronized calls.
numOps = 5;
for (int ctr = 0; ctr <= 4; ctr++)
ThreadPool.QueueUserWorkItem(new WaitCallback(UnSyncUpdateResource));
// Wait until this WaitHandle is signaled.
opsAreDone.WaitOne();
Console.WriteLine("\t\nAll unsynchronized thread operations have completed.\n");
}
static void SyncUpdateResource(Object state)
{
// Call the internal synchronized method.
SyncRes.Access();
// Ensure that only one thread can decrement the counter at a time.
if (Interlocked.Decrement(ref numOps) == 0)
// Announce to Main that in fact all thread calls are done.
opsAreDone.Set();
}
static void UnSyncUpdateResource(Object state)
{
// Call the unsynchronized method.
UnSyncRes.Access();
// Ensure that only one thread can decrement the counter at a time.
if (Interlocked.Decrement(ref numOps) == 0)
// Announce to Main that in fact all thread calls are done.
opsAreDone.Set();
}
}
// The example displays output like the following:
// Starting synchronized resource access on thread #6
// Stopping synchronized resource access on thread #6
// Starting synchronized resource access on thread #7
// Stopping synchronized resource access on thread #7
// Starting synchronized resource access on thread #3
// Stopping synchronized resource access on thread #3
// Starting synchronized resource access on thread #4
// Stopping synchronized resource access on thread #4
// Starting synchronized resource access on thread #5
// Stopping synchronized resource access on thread #5
//
// All synchronized operations have completed.
//
// Starting unsynchronized resource access on Thread #7
// Starting unsynchronized resource access on Thread #9
// Starting unsynchronized resource access on Thread #10
// Starting unsynchronized resource access on Thread #6
// Starting unsynchronized resource access on Thread #3
// Stopping unsynchronized resource access on thread #7
// Stopping unsynchronized resource access on thread #9
// Stopping unsynchronized resource access on thread #3
// Stopping unsynchronized resource access on thread #10
// Stopping unsynchronized resource access on thread #6
//
// All unsynchronized thread operations have completed.
Imports System.Threading
Friend Class SyncResource
' Use a monitor to enforce synchronization.
Public Sub Access()
SyncLock Me
Console.WriteLine("Starting synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
Thread.Sleep(2000)
End If
Thread.Sleep(200)
Console.WriteLine("Stopping synchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
End SyncLock
End Sub
End Class
Friend Class UnSyncResource
' Do not enforce synchronization.
Public Sub Access()
Console.WriteLine("Starting unsynchronized resource access on Thread #{0}",
Thread.CurrentThread.ManagedThreadId)
If Thread.CurrentThread.ManagedThreadId Mod 2 = 0 Then
Thread.Sleep(2000)
End If
Thread.Sleep(200)
Console.WriteLine("Stopping unsynchronized resource access on thread #{0}",
Thread.CurrentThread.ManagedThreadId)
End Sub
End Class
Public Module App
Private numOps As Integer
Private opsAreDone As New AutoResetEvent(False)
Private SyncRes As New SyncResource()
Private UnSyncRes As New UnSyncResource()
Public Sub Main()
' Set the number of synchronized calls.
numOps = 5
For ctr As Integer = 0 To 4
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf SyncUpdateResource))
Next
' Wait until this WaitHandle is signaled.
opsAreDone.WaitOne()
Console.WriteLine(vbTab + Environment.NewLine + "All synchronized operations have completed.")
Console.WriteLine()
numOps = 5
' Reset the count for unsynchronized calls.
For ctr As Integer = 0 To 4
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf UnSyncUpdateResource))
Next
' Wait until this WaitHandle is signaled.
opsAreDone.WaitOne()
Console.WriteLine(vbTab + Environment.NewLine + "All unsynchronized thread operations have completed.")
End Sub
Sub SyncUpdateResource()
' Call the internal synchronized method.
SyncRes.Access()
' Ensure that only one thread can decrement the counter at a time.
If Interlocked.Decrement(numOps) = 0 Then
' Announce to Main that in fact all thread calls are done.
opsAreDone.Set()
End If
End Sub
Sub UnSyncUpdateResource()
' Call the unsynchronized method.
UnSyncRes.Access()
' Ensure that only one thread can decrement the counter at a time.
If Interlocked.Decrement(numOps) = 0 Then
' Announce to Main that in fact all thread calls are done.
opsAreDone.Set()
End If
End Sub
End Module
' The example displays output like the following:
' Starting synchronized resource access on thread #6
' Stopping synchronized resource access on thread #6
' Starting synchronized resource access on thread #7
' Stopping synchronized resource access on thread #7
' Starting synchronized resource access on thread #3
' Stopping synchronized resource access on thread #3
' Starting synchronized resource access on thread #4
' Stopping synchronized resource access on thread #4
' Starting synchronized resource access on thread #5
' Stopping synchronized resource access on thread #5
'
' All synchronized operations have completed.
'
' Starting unsynchronized resource access on Thread #7
' Starting unsynchronized resource access on Thread #9
' Starting unsynchronized resource access on Thread #10
' Starting unsynchronized resource access on Thread #6
' Starting unsynchronized resource access on Thread #3
' Stopping unsynchronized resource access on thread #7
' Stopping unsynchronized resource access on thread #9
' Stopping unsynchronized resource access on thread #3
' Stopping unsynchronized resource access on thread #10
' Stopping unsynchronized resource access on thread #6
'
' All unsynchronized thread operations have completed.
A példa egy változót határoz meg, numOpsamely meghatározza az erőforrás eléréséhez megkísérlendő szálak számát. Az alkalmazáslánc ötször hívja meg a ThreadPool.QueueUserWorkItem(WaitCallback) szinkronizált és a nem szinkronizált hozzáférés metódusát. A ThreadPool.QueueUserWorkItem(WaitCallback) metódus egyetlen paraméterrel rendelkezik, egy meghatalmazott, amely nem fogad el paramétereket, és nem ad vissza értéket. Szinkronizált hozzáférés esetén meghívja a SyncUpdateResource metódust; a nem aszinkron hozzáférés esetén meghívja a metódust UnSyncUpdateResource . Minden egyes metódushívás után az alkalmazásszál meghívja az AutoResetEvent.WaitOne metódust, hogy blokkolja a AutoResetEvent példány jelzéséig.
A SyncUpdateResource metódus minden egyes hívása meghívja a belső SyncResource.Access metódust, majd meghívja a Interlocked.Decrement metódust a numOps számláló csökkentésére. A Interlocked.Decrement metódus a számláló dekrementálására szolgál, mert ellenkező esetben nem lehet biztos abban, hogy egy második szál hozzáfér az értékhez, mielőtt az első szál decrementált értékét a változóban tárolnák. Amikor az utolsó szinkronizált feldolgozószál nullára állítja a számlálót, ami azt jelzi, hogy az összes szinkronizált szál hozzáfért az erőforráshoz, a SyncUpdateResource metódus meghívja a EventWaitHandle.Set metódust, amely a fő szálat a végrehajtás folytatására jelzi.
A UnSyncUpdateResource metódus minden egyes hívása meghívja a belső UnSyncResource.Access metódust, majd meghívja a Interlocked.Decrement metódust a numOps számláló csökkentésére. A metódus ismét a Interlocked.Decrement számláló dekrementálására szolgál, hogy egy második szál ne férhessen hozzá az értékhez, mielőtt az első szál decrementált értékét hozzárendelték a változóhoz. Amikor az utolsó nem aszinkron munkaszál nullára állítja a számlálót, ami azt jelzi, hogy nincs szükség több nem szinkronizált szálra az erőforrás eléréséhez, a UnSyncUpdateResource metódus meghívja a EventWaitHandle.Set metódust, amely a fő szálat jelzi a végrehajtás folytatásához.
Ahogy a példa kimenete is mutatja, a szinkronizált hozzáférés biztosítja, hogy a hívószál kilépjen a védett erőforrásból, mielőtt egy másik szál hozzáférhet hozzá; minden szál várakozik az elődjén. Másrészt a zárolás nélkül a UnSyncResource.Access metódust abban a sorrendben hívjuk meg, amelyben a szálak elérik.
Megjegyzések
Az Monitor osztály lehetővé teszi a kód egy régiójához való hozzáférés szinkronizálását egy adott objektum zárolásának felvételével és felszabadításával a Monitor.Enter, Monitor.TryEnterés Monitor.Exit metódusok meghívásával. Az objektumzárak lehetővé teszik a kódblokkokhoz való hozzáférés korlátozását, amelyet gyakran kritikus szakasznak neveznek. Bár egy szál egy objektum zárolását birtokolja, más szál nem szerezheti be ezt a zárolást. Az osztály használatával Monitor azt is biztosíthatja, hogy más szál ne férhessen hozzá a zárolástulajdonos által végrehajtott alkalmazáskód egy szakaszához, kivéve, ha a másik szál egy másik zárolt objektummal hajtja végre a kódot. Mivel a Monitor osztály szál affinitással rendelkezik, a zárolást beszerző szálnak fel kell szabadítania a zárolást a Monitor.Exit metódus meghívásával.
Áttekintés
Monitor a következő funkciókkal rendelkezik:
- Igény szerinti objektumhoz van társítva.
- Ez kötetlen, ami azt jelenti, hogy közvetlenül bármely környezetből meghívható.
- Az osztály egy példánya Monitor nem hozható létre; az Monitor osztály metódusai mind statikusak. A rendszer minden metódust átad a kritikus szakaszhoz való hozzáférést vezérlő szinkronizált objektumnak.
Note
Az osztály használatával a Monitor sztringeken kívül más objektumokat (azaz nem hivatkozástípusokat String) zárolhat, nem értéktípusokat. További részletekért tekintse meg a Enter metódus túlterheléseit és a zárolási objektum szakaszt a cikk későbbi részében.
Az alábbi táblázat a szinkronizált objektumokhoz hozzáférő szálak által végrehajtható műveleteket ismerteti:
| Action | Description |
|---|---|
| Enter, TryEnter | Egy objektumhoz zárolást szerez. Ez a művelet egy kritikus szakasz kezdetét is jelzi. Más szál csak akkor léphet be a kritikus szakaszba, ha a kritikus szakaszban lévő utasításokat egy másik zárolt objektummal hajtja végre. |
| Wait | Feloldja a zárolást egy objektumon, hogy más szálak zárolhassák és elérhessék az objektumot. A hívószál megvárja, amíg egy másik szál hozzáfér az objektumhoz. A rendszer impulzusjelekkel értesíti a várakozó szálakat az objektum állapotának változásairól. |
| Pulse (jel), PulseAll | Jelet küld egy vagy több várakozó szálnak. A jel értesíti a várakozó szálat, hogy a zárolt objektum állapota megváltozott, és a zárolás tulajdonosa készen áll a zárolás feloldására. A várakozási szál az objektum kész várakozási sorába kerül, hogy végül megszerezhesse az objektum zárolását. Miután a szál megkapta a zárolást, ellenőrizheti az objektum új állapotát, és ellenőrizheti, hogy elérte-e a szükséges állapotot. |
| Exit | Feloldja a zárolást egy objektumon. Ez a művelet a zárolt objektum által védett kritikus szakasz végét is jelzi. |
Két túlterhelési csoport van az Enter és a TryEnter metódusok számára. Egy túlterhelési csoport rendelkezik egy ref (C#-ban) vagy ByRef (Visual Basicben) Boolean paraméterrel, amelyet atomilag true-ra állítanak be a zárolás megszerzése esetén, még akkor is, ha kivétel történik a zárolás megszerzésekor. Ezeket a túlterheléseket akkor használja, ha minden esetben kritikus fontosságú a zárolás feloldása, még akkor is, ha a zárolás által védett erőforrások nem konzisztens állapotban vannak.
A zárolási objektum
A Monitor osztály static metódusokból áll (Shared a Visual Basicben), amelyek egy olyan objektumon működnek, amely szabályozza a kritikus szakaszhoz való hozzáférést. Az egyes szinkronizált objektumok esetében a következő információk maradnak fenn:
- A szálra mutató hivatkozás, amely jelenleg birtokolja a lockot.
- Egy kész sorra mutató hivatkozás, amely tartalmazza azokat a szálakat, amelyek készen állnak a zárolás megszerzésére.
- Egy várakozási üzenetsorra mutató hivatkozás, amely tartalmazza azokat a szálakat, amelyek a zárolt objektum állapotának változására várnak.
Monitor objektumokat (azaz hivatkozástípusokat) zárol, nem értéktípusokat. Bár átadhat egy értéktípust mind Enter, mind Exit számára, minden egyes híváshoz külön van boxolva. Mivel minden hívás külön objektumot hoz létre, Enter soha nem blokkol, és a kód, amelyet állítólag véd, valójában nem szinkronizált. Emellett az átadott Exit objektum eltér az átadott Enterobjektumtól, ezért Monitor kivételt SynchronizationLockException jelez az "Objektumszinkronizálási módszer meghívása nem aszinkron kódblokkból" üzenettel.
Az alábbi példa ezt a problémát szemlélteti. A rendszer tíz feladatot indít el, amelyek mindegyike csak 250 milliszekundumig alszik. Minden tevékenység ezután frissít egy számlálóváltozót, nTasksamely a ténylegesen elindított és végrehajtott tevékenységek számának megszámlálására szolgál. Mivel nTasks egy olyan globális változó, amelyet egyszerre több tevékenység is frissíthet, a figyelő több tevékenység egyidejű módosításával szembeni védelmére szolgál. Ahogy azonban a példa kimenete is mutatja, mindegyik feladat kivételt SynchronizationLockException dob.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
int nTasks = 0;
List<Task> tasks = new List<Task>();
try {
for (int ctr = 0; ctr < 10; ctr++)
tasks.Add(Task.Run( () => { // Instead of doing some work, just sleep.
Thread.Sleep(250);
// Increment the number of tasks.
Monitor.Enter(nTasks);
try {
nTasks += 1;
}
finally {
Monitor.Exit(nTasks);
}
} ));
Task.WaitAll(tasks.ToArray());
Console.WriteLine("{0} tasks started and executed.", nTasks);
}
catch (AggregateException e) {
String msg = String.Empty;
foreach (var ie in e.InnerExceptions) {
Console.WriteLine("{0}", ie.GetType().Name);
if (!msg.Contains(ie.Message))
msg += ie.Message + Environment.NewLine;
}
Console.WriteLine("\nException Message(s):");
Console.WriteLine(msg);
}
}
}
// The example displays the following output:
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
// SynchronizationLockException
//
// Exception Message(s):
// Object synchronization method was called from an unsynchronized block of code.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example3
Public Sub Main()
Dim nTasks As Integer = 0
Dim tasks As New List(Of Task)()
Try
For ctr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
' Instead of doing some work, just sleep.
Thread.Sleep(250)
' Increment the number of tasks.
Monitor.Enter(nTasks)
Try
nTasks += 1
Finally
Monitor.Exit(nTasks)
End Try
End Sub))
Next
Task.WaitAll(tasks.ToArray())
Console.WriteLine("{0} tasks started and executed.", nTasks)
Catch e As AggregateException
Dim msg As String = String.Empty
For Each ie In e.InnerExceptions
Console.WriteLine("{0}", ie.GetType().Name)
If Not msg.Contains(ie.Message) Then
msg += ie.Message + Environment.NewLine
End If
Next
Console.WriteLine(vbCrLf + "Exception Message(s):")
Console.WriteLine(msg)
End Try
End Sub
End Module
' The example displays the following output:
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
' SynchronizationLockException
'
' Exception Message(s):
' Object synchronization method was called from an unsynchronized block of code.
Minden tevékenység kivételt SynchronizationLockException jelez, mert a nTasks változó az egyes tevékenységek metódusának Monitor.Enter hívása előtt be van jelölve. Más szóval minden metódushívás egy külön változót ad át, amely független a többiekétől.
nTasks ismét dobozolva van a Monitor.Exit metódus hívásában. Ez ismét létrehoz tíz új, egymástól független dobozos változót, nTasksvalamint a metódus hívásában Monitor.Enter létrehozott tíz dobozos változót. A kivételt akkor a rendszer kibocsátja, mert a kód egy korábban nem zárolt, újonnan létrehozott változó zárolását próbálja feloldani.
Habár lehetőség van egy értéktípusú változót dobozolni a következő példában bemutatott módon, majd ugyanazt a dobozolt objektumot mindkét metódusnak továbbadni, ennek nincs előnye. A nem beérkezett változó módosításai nem jelennek meg a dobozos másolatban, és nincs mód a dobozos másolat értékének módosítására.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
int nTasks = 0;
object o = nTasks;
List<Task> tasks = new List<Task>();
try {
for (int ctr = 0; ctr < 10; ctr++)
tasks.Add(Task.Run( () => { // Instead of doing some work, just sleep.
Thread.Sleep(250);
// Increment the number of tasks.
Monitor.Enter(o);
try {
nTasks++;
}
finally {
Monitor.Exit(o);
}
} ));
Task.WaitAll(tasks.ToArray());
Console.WriteLine("{0} tasks started and executed.", nTasks);
}
catch (AggregateException e) {
String msg = String.Empty;
foreach (var ie in e.InnerExceptions) {
Console.WriteLine("{0}", ie.GetType().Name);
if (!msg.Contains(ie.Message))
msg += ie.Message + Environment.NewLine;
}
Console.WriteLine("\nException Message(s):");
Console.WriteLine(msg);
}
}
}
// The example displays the following output:
// 10 tasks started and executed.
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks
Module Example2
Public Sub Main()
Dim nTasks As Integer = 0
Dim o As Object = nTasks
Dim tasks As New List(Of Task)()
Try
For ctr As Integer = 0 To 9
tasks.Add(Task.Run(Sub()
' Instead of doing some work, just sleep.
Thread.Sleep(250)
' Increment the number of tasks.
Monitor.Enter(o)
Try
nTasks += 1
Finally
Monitor.Exit(o)
End Try
End Sub))
Next
Task.WaitAll(tasks.ToArray())
Console.WriteLine("{0} tasks started and executed.", nTasks)
Catch e As AggregateException
Dim msg As String = String.Empty
For Each ie In e.InnerExceptions
Console.WriteLine("{0}", ie.GetType().Name)
If Not msg.Contains(ie.Message) Then
msg += ie.Message + Environment.NewLine
End If
Next
Console.WriteLine(vbCrLf + "Exception Message(s):")
Console.WriteLine(msg)
End Try
End Sub
End Module
' The example displays the following output:
' 10 tasks started and executed.
Ha kiválaszt egy objektumot, amelyen szinkronizálni szeretne, csak privát vagy belső objektumokra kell zárolnia. A külső objektumok zárolása holtpontot eredményezhet, mivel a nem kapcsolódó kód ugyanazokat az objektumokat választhatja ki, amelyek különböző célokra zárolhatók.
Vegye figyelembe, hogy egy objektumot szinkronizálhat több alkalmazástartományban, ha a zároláshoz használt objektum származik a MarshalByRefObject-ból.
A kritikus szakasz
A Enter és Exit metódussal jelölheti egy kritikus szakasz elejét és végét.
Note
A Enter és Exit metódusok által biztosított funkciók megegyeznek a C# lock utasítása és a Visual Basic SyncLock utasítása által biztosított funkciókkal, azzal a különbséggel, hogy a nyelvi szerkezetek a Monitor.Enter(Object, Boolean) metódus túlterhelést és a Monitor.Exit metódust a try ... beburkolják.
finally blokkot a monitor kiadásának biztosításához.
Ha a kritikus szakasz egy összefüggő utasítások halmaza, akkor a metódus által Enter beszerzett zárolás garantálja, hogy csak egyetlen szál tudja végrehajtani a zárt kódot a zárolt objektummal. Ebben az esetben azt javasoljuk, hogy a kódot egy try blokkba helyezze, és helyezze el a metódus meghívását Exit egy finally blokkban. Ez biztosítja, hogy a zárolás kivétel esetén is feloldva legyen. A következő kódrészlet ezt a mintát szemlélteti.
// Define the lock object.
var obj = new Object();
// Define the critical section.
Monitor.Enter(obj);
try {
// Code to execute one thread at a time.
}
// catch blocks go here.
finally {
Monitor.Exit(obj);
}
' Define the lock object.
Dim obj As New Object()
' Define the critical section.
Monitor.Enter(obj)
Try
' Code to execute one thread at a time.
' catch blocks go here.
Finally
Monitor.Exit(obj)
End Try
Ezt a létesítményt általában egy osztály statikus vagy példánymetódusához való hozzáférés szinkronizálására használják.
Ha egy kritikus szakasz egy teljes metódusra terjed ki, a zárolás a System.Runtime.CompilerServices.MethodImplAttribute elhelyezésével valósítható meg a metóduson, és a Synchronized konstruktorban a System.Runtime.CompilerServices.MethodImplAttribute értékének megadásával. Ha ezt az attribútumot használja, a Enter és Exit metódushívásokra nincs szükség. A következő kódrészlet a következő mintát szemlélteti:
[MethodImplAttribute(MethodImplOptions.Synchronized)]
void MethodToLock()
{
// Method implementation.
}
<MethodImplAttribute(MethodImplOptions.Synchronized)>
Sub MethodToLock()
' Method implementation.
End Sub
Vegye figyelembe, hogy az attribútum miatt az aktuális szál addig tartja a zárolást, amíg a metódus vissza nem tér; ha a zárolás hamarabb feloldható, használja az Monitor osztályt, a C# zárolási utasítást vagy a Visual Basic SyncLock utasítást a metóduson belül az attribútum helyett.
Bár lehetséges, hogy az adott objektumot zároló Enter és felszabadító Exit utasítások átíveljenek tag- vagy osztályhatárokon, vagy mindkettőn, ez a gyakorlat nem ajánlott.
Pulse, PulseAll és Wait
Miután egy szál birtokolja a zárolást, és belépett a zárolás által védett kritikus szakaszba, meghívhatja a Monitor.Wait, Monitor.Pulseés Monitor.PulseAll metódusokat.
Amikor a zárolást tartó szál meghívja a Wait, a zárolás feloldódik, és a szál hozzáadódik a szinkronizált objektum várakozási sorához. Ha van kész sorban, a kész várakozási sor első szála megszerzi a zárat, és belép a kritikus szakaszba. A Wait szál a várakozási sorból áthelyeződik a készlet sorba, amikor a zárolást tartó szál meghívja a Monitor.Pulse vagy a Monitor.PulseAll metódust (az áthelyezéshez a szálnak a várakozási sor élén kell lennie). A Wait metódus akkor tér vissza, amikor a hívószál visszaszerzi a zárolást.
Amikor az a szál, amely a zárat tartja, meghívja a Pulse-t, a várakozási sor elején lévő szál átkerül a kész sorba. A metódus PulseAll hívása az összes szálat áthelyezi a várakozási sorból a kész sorba.
Monitorozási és várakozási fogópontok
Fontos megjegyezni a Monitor osztály és a WaitHandle objektumok használata közötti különbséget.
- Az Monitor osztály tisztán felügyelt, teljes mértékben hordozható, és az operációs rendszer erőforrásigényét tekintve hatékonyabb lehet.
- WaitHandle Az objektumok az operációs rendszer várakozásra váró objektumait jelölik, hasznosak a felügyelt és a nem felügyelt kód közötti szinkronizáláshoz, és elérhetővé tehetnek néhány speciális operációsrendszer-funkciót, például azt, hogy egyszerre több objektumon is várakozhat.
Tulajdonságok
| Name | Description |
|---|---|
| LockContentionCount |
Lekérdezi, hogy hányszor volt versengés a monitor zárolásának megkísérlésekor. |
Metódusok
| Name | Description |
|---|---|
| Enter(Object, Boolean) |
Kizárólagos zárolást szerez be a megadott objektumon, és atomilag beállít egy értéket, amely jelzi, hogy a zárolás megtörtént-e. |
| Enter(Object) |
Kizárólagos zárolást szerez be a megadott objektumon. |
| Exit(Object) |
Kizárólagos zárolást ad ki a megadott objektumon. |
| IsEntered(Object) |
Meghatározza, hogy az aktuális szál tartja-e a zárolást a megadott objektumon. |
| Pulse(Object) |
Értesíti a várólistán lévő szálat a zárolt objektum állapotának változásáról. |
| PulseAll(Object) |
Értesíti az objektum állapotában bekövetkező változás minden várakozó szálát. |
| TryEnter(Object, Boolean) |
Megkísérli megszerezni a megadott objektum kizárólagos zárolását, és atomilag beállít egy értéket, amely jelzi, hogy a zárolás megtörtént-e. |
| TryEnter(Object, Int32, Boolean) |
A megadott számú ezredmásodpercben megkísérel kizárólagos zárolást szerezni a megadott objektumon, és atomilag beállít egy értéket, amely jelzi, hogy a zárolás megtörtént-e. |
| TryEnter(Object, Int32) |
A megadott számú ezredmásodpercben megkísérel kizárólagos zárolást szerezni a megadott objektumon. |
| TryEnter(Object, TimeSpan, Boolean) |
Megkísérli a megadott ideig kizárólagos zárolást szerezni a megadott objektumon, és atomilag beállít egy értéket, amely jelzi, hogy a zárolás megtörtént-e. |
| TryEnter(Object, TimeSpan) |
Megkísérli a megadott ideig kizárólagos zárolást szerezni a megadott objektumon. |
| TryEnter(Object) |
Megkísérli a megadott objektum kizárólagos zárolásának beszerzését. |
| Wait(Object, Int32, Boolean) |
Feloldja a zárolást egy objektumon, és letiltja az aktuális szálat, amíg újra meg nem oldja a zárolást. Ha a megadott időtúllépési időköz eltelik, a szál belép a kész üzenetsorba. Ez a módszer azt is meghatározza, hogy a környezet szinkronizálási tartománya (ha szinkronizált környezetben van) a várakozás előtt ki van-e lépve, majd később újból meg legyen-e állítva. |
| Wait(Object, Int32) |
Feloldja a zárolást egy objektumon, és letiltja az aktuális szálat, amíg újra meg nem oldja a zárolást. Ha a megadott időtúllépési időköz eltelik, a szál belép a kész üzenetsorba. |
| Wait(Object, TimeSpan, Boolean) |
Feloldja a zárolást egy objektumon, és letiltja az aktuális szálat, amíg újra meg nem oldja a zárolást. Ha a megadott időtúllépési időköz eltelik, a szál belép a kész üzenetsorba. Ha szeretné, a várakozás előtt kilép a szinkronizálási tartományból a szinkronizált környezethez, majd később újból leküldi a tartományt. |
| Wait(Object, TimeSpan) |
Feloldja a zárolást egy objektumon, és letiltja az aktuális szálat, amíg újra meg nem oldja a zárolást. Ha a megadott időtúllépési időköz eltelik, a szál belép a kész üzenetsorba. |
| Wait(Object) |
Feloldja a zárolást egy objektumon, és letiltja az aktuális szálat, amíg újra meg nem oldja a zárolást. |
A következőre érvényes:
Szálbiztonság
Ez a típus szálbiztos.