System.Threading.Monitor-klasse
In dit artikel vindt u aanvullende opmerkingen in de referentiedocumentatie voor deze API.
Met Monitor de klasse kunt u de toegang tot een codegebied synchroniseren door een vergrendeling op een bepaald object te nemen en los te laten door de Monitor.Enter, Monitor.TryEnteren Monitor.Exit methoden aan te roepen. Objectvergrendelingen bieden de mogelijkheid om de toegang tot een codeblok te beperken, ook wel een kritieke sectie genoemd. Hoewel een thread eigenaar is van de vergrendeling voor een object, kan er geen andere thread die vergrendeling verkrijgen. U kunt de Monitor klasse ook gebruiken om ervoor te zorgen dat er geen andere thread toegang heeft tot een sectie met toepassingscode die wordt uitgevoerd door de eigenaar van de vergrendeling, tenzij de andere thread de code uitvoert met behulp van een ander vergrendeld object. Omdat de klasse Monitor threadaffiniteit heeft, moet de thread die een vergrendeling heeft verkregen, de vergrendeling vrijgeven door de methode Monitor.Exit aan te roepen.
Overzicht
Monitor heeft de volgende functies:
- Het is gekoppeld aan een object op aanvraag.
- Het is niet-afhankelijk, wat betekent dat het rechtstreeks vanuit elke context kan worden aangeroepen.
- Er kan geen exemplaar van de Monitor klasse worden gemaakt. De methoden van de Monitor klasse zijn allemaal statisch. Elke methode wordt doorgegeven aan het gesynchroniseerde object waarmee de toegang tot de kritieke sectie wordt beheerd.
Notitie
Gebruik de Monitor klasse om andere objecten dan tekenreeksen (dat wil gezegd, verwijzingstypen anders dan String), niet waardetypen te vergrendelen. Zie de overbelasting van de Enter methode en de sectie Vergrendelingsobject verderop in dit artikel voor meer informatie.
In de volgende tabel worden de acties beschreven die kunnen worden uitgevoerd door threads die toegang hebben tot gesynchroniseerde objecten:
Actie | Beschrijving |
---|---|
Enter, TryEnter | Hiermee verkrijgt u een vergrendeling voor een object. Deze actie markeert ook het begin van een kritieke sectie. Er kan geen andere thread de kritieke sectie invoeren, tenzij de instructies in de kritieke sectie worden uitgevoerd met behulp van een ander vergrendeld object. |
Wait | Hiermee wordt de vergrendeling op een object vrijgegeven om andere threads toe te staan het object te vergrendelen en te openen. De aanroepende thread wacht terwijl een andere thread toegang heeft tot het object. Pulssignalen worden gebruikt om wachtende threads op de hoogte te stellen van wijzigingen in de status van een object. |
Pulse (signaal), PulseAll | Hiermee wordt een signaal verzonden naar een of meer wachtthreads. Het signaal meldt een wachtthread dat de status van het vergrendelde object is gewijzigd en de eigenaar van de vergrendeling gereed is om de vergrendeling vrij te geven. De wachtthread wordt in de wachtrij voor het object geplaatst, zodat deze uiteindelijk de vergrendeling voor het object kan ontvangen. Zodra de thread de vergrendeling heeft, kan deze de nieuwe status van het object controleren om te zien of de vereiste status is bereikt. |
Exit | Hiermee wordt de vergrendeling van een object vrijgegeven. Deze actie markeert ook het einde van een kritieke sectie die wordt beveiligd door het vergrendelde object. |
Er zijn twee sets van overbelastingen voor de Enter en TryEnter methoden. Een set overbelastingen heeft een ref
parameter (in C#) of ByRef
(in Visual Basic) Boolean die atomisch is ingesteld true
als de vergrendeling wordt verkregen, zelfs als er een uitzondering wordt gegenereerd bij het verkrijgen van de vergrendeling. Gebruik deze overbelastingen als het essentieel is om de vergrendeling in alle gevallen vrij te geven, zelfs als de resources die de vergrendeling beveiligt, mogelijk niet consistent zijn.
Het vergrendelingsobject
De klasse Monitor bestaat uit static
(Shared
in Visual Basic)-methoden die worden uitgevoerd op een object waarmee de toegang tot de kritieke sectie wordt beheerd. De volgende informatie wordt bijgehouden voor elk gesynchroniseerd object:
- Een verwijzing naar de thread die momenteel de vergrendeling bevat.
- Een verwijzing naar een wachtrij die gereed is voor de threads die gereed zijn om de vergrendeling te verkrijgen.
- Een verwijzing naar een wachtwachtrij, die de threads bevat die wachten op melding van een wijziging in de status van het vergrendelde object.
Monitor vergrendelt objecten (dat wil gezegd, verwijzingstypen), geen waardetypen. Hoewel u een waardetype kunt doorgeven aan Enter en Exit, wordt het voor elke aanroep afzonderlijk in een vak geplaatst. Omdat elke aanroep een afzonderlijk object maakt, Enter nooit blokkeert en de code die het beveiligt, wordt niet echt gesynchroniseerd. Bovendien verschilt het object waarnaar wordt doorgegeven Exit , van het object dat wordt doorgegeven aan Enter, dus Monitor genereert SynchronizationLockException een uitzondering met het bericht 'Objectsynchronisatiemethode is aangeroepen vanuit een niet-gesynchroniseerd codeblok'.
In het volgende voorbeeld ziet u dit probleem. Het start tien taken, die elk slechts 250 milliseconden slapen. Elke taak werkt vervolgens een tellervariabele bij, nTasks
die bedoeld is om het aantal taken te tellen dat daadwerkelijk is gestart en uitgevoerd. Omdat nTasks
dit een globale variabele is die tegelijkertijd door meerdere taken kan worden bijgewerkt, wordt een monitor gebruikt om deze te beschermen tegen gelijktijdige wijzigingen door meerdere taken. Zoals echter in de uitvoer uit het voorbeeld wordt weergegeven, genereert elk van de taken een SynchronizationLockException uitzondering.
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.
Elke taak genereert een SynchronizationLockException uitzondering omdat de nTasks
variabele wordt geplaatst vóór de aanroep van de Monitor.Enter methode in elke taak. Met andere woorden, elke methode-aanroep wordt doorgegeven aan een afzonderlijke variabele die onafhankelijk is van de andere. nTasks
wordt opnieuw in de aanroep naar de Monitor.Exit methode geplaatst. Nogmaals, hiermee worden tien nieuwe boxed variabelen gemaakt, die onafhankelijk van elkaar zijn, nTasks
en de tien vakkenvariabelen die zijn gemaakt in de aanroep naar de Monitor.Enter methode. De uitzondering wordt vervolgens gegenereerd, omdat onze code probeert een vergrendeling vrij te geven op een zojuist gemaakte variabele die niet eerder is vergrendeld.
Hoewel u een waardetypevariabele kunt gebruiken voordat u aanroept Enter en Exit, zoals wordt weergegeven in het volgende voorbeeld, hetzelfde boxed object doorgeeft aan beide methoden, is dit geen voordeel. Wijzigingen in de variabele zonder postvak worden niet doorgevoerd in de vakkenkopie en de waarde van de in het vak geplaatste kopie kan niet worden gewijzigd.
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.
Wanneer u een object selecteert waarop u wilt synchroniseren, moet u alleen vergrendelen voor privé- of interne objecten. Het vergrendelen van externe objecten kan leiden tot impasses, omdat niet-gerelateerde code dezelfde objecten kan kiezen die moeten worden vergrendeld voor verschillende doeleinden.
Houd er rekening mee dat u kunt synchroniseren op een object in meerdere toepassingsdomeinen als het object dat wordt gebruikt voor de vergrendeling is afgeleid van MarshalByRefObject.
De kritieke sectie
Gebruik de Enter en Exit methoden om het begin en einde van een kritieke sectie te markeren.
Notitie
De functionaliteit van de Enter en methoden is identiek aan die van de vergrendelingsinstructie in C# en de SyncLock-instructiein Visual Basic, behalve dat de taalconstructies de overbelasting van de Monitor.Enter(Object, Boolean) methode en de Monitor.Exit methode in een try
Exit ...finally
om ervoor te zorgen dat de monitor wordt vrijgegeven.
Als de kritieke sectie een reeks aaneengesloten instructies is, garandeert de vergrendeling die door de Enter methode is verkregen dat slechts één thread de ingesloten code met het vergrendelde object kan uitvoeren. In dit geval raden we u aan die code in een try
blok te plaatsen en de aanroep naar de Exit methode in een finally
blok te plaatsen. Dit zorgt ervoor dat de vergrendeling wordt vrijgegeven, zelfs als er een uitzondering optreedt. Het volgende codefragment illustreert dit patroon.
// 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
Deze faciliteit wordt doorgaans gebruikt om de toegang tot een statische of instantiemethode van een klasse te synchroniseren.
Als een kritieke sectie een hele methode omvat, kan de vergrendelingsfaciliteit worden bereikt door de System.Runtime.CompilerServices.MethodImplAttribute methode te plaatsen en de Synchronized waarde in de constructor van System.Runtime.CompilerServices.MethodImplAttribute. Wanneer u dit kenmerk gebruikt, zijn de Enter aanroepen en Exit methoden niet nodig. Het volgende codefragment illustreert dit patroon:
[MethodImplAttribute(MethodImplOptions.Synchronized)]
void MethodToLock()
{
// Method implementation.
}
<MethodImplAttribute(MethodImplOptions.Synchronized)>
Sub MethodToLock()
' Method implementation.
End Sub
Houd er rekening mee dat het kenmerk ervoor zorgt dat de huidige thread de vergrendeling vasthoudt totdat de methode wordt geretourneerd; als de vergrendeling sneller kan worden vrijgegeven, gebruikt u de Monitor klasse, de C# -vergrendelingsinstructie of de Visual Basic SyncLock-instructie in de methode in plaats van het kenmerk.
Hoewel het mogelijk is voor de Enter en Exit instructies die een bepaald object vergrendelen en vrijgeven aan grensoverschrijdende of klassengrenzen of beide, wordt deze procedure niet aanbevolen.
Pulse, PulseAll en Wait
Zodra een thread eigenaar is van de vergrendeling en de kritieke sectie heeft ingevoerd die door de vergrendeling wordt beveiligd, kan deze de Monitor.Wait, Monitor.Pulseen Monitor.PulseAll methoden aanroepen.
Wanneer de thread die de vergrendelingsaanroepen Waitbevat, wordt de vergrendeling vrijgegeven en wordt de thread toegevoegd aan de wachtrij van het gesynchroniseerde object. De eerste thread in de wachtrij gereed, indien aanwezig, verkrijgt de vergrendeling en voert de kritieke sectie in. De thread die wordt aangeroepen Wait , wordt verplaatst van de wachtrij naar de wachtrij die gereed is wanneer de Monitor.Pulse of de Monitor.PulseAll methode wordt aangeroepen door de thread die de vergrendeling bevat (om te worden verplaatst, moet de thread zich boven aan de wachtrij bevinden). De Wait methode wordt geretourneerd wanneer de aanroepende thread de vergrendeling opnieuw aanvraagt.
Wanneer de thread met de vergrendelingsaanroepen Pulsebevat, wordt de thread aan het hoofd van de wachtrij verplaatst naar de wachtrij die gereed is. Met de aanroep naar de PulseAll methode worden alle threads van de wachtrij naar de wachtrij verplaatst.
Monitors en wachtgrepen
Het is belangrijk om het onderscheid te noteren tussen het gebruik van de Monitor klasse en WaitHandle objecten.
- De Monitor klasse is puur beheerd, volledig draagbaar en kan efficiënter zijn in termen van resourcevereisten van het besturingssysteem.
- WaitHandle objecten vertegenwoordigen wachtbare objecten van het besturingssysteem, zijn handig voor het synchroniseren tussen beheerde en onbeheerde code en bieden een aantal geavanceerde besturingssysteemfuncties, zoals de mogelijkheid om te wachten op veel objecten tegelijk.
Voorbeelden
In het volgende voorbeeld wordt de Monitor klasse gebruikt om de toegang tot één exemplaar van een generator voor willekeurige getallen die wordt vertegenwoordigd door de Random klasse te synchroniseren. In het voorbeeld worden tien taken gemaakt, die elk asynchroon worden uitgevoerd op een threadgroepthread. Elke taak genereert 10.000 willekeurige getallen, berekent het gemiddelde en werkt twee variabelen op procedureniveau bij waarmee een voorlopig totaal wordt bijgehouden van het aantal gegenereerde willekeurige getallen en de som ervan. Nadat alle taken zijn uitgevoerd, worden deze twee waarden vervolgens gebruikt om het totale gemiddelde te berekenen.
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)
Omdat ze kunnen worden geopend vanaf elke taak die wordt uitgevoerd op een threadgroepthread, moet de toegang tot de variabelen total
en n
ook worden gesynchroniseerd. De Interlocked.Add methode wordt hiervoor gebruikt.
In het volgende voorbeeld ziet u het gecombineerde gebruik van de Monitor klasse (geïmplementeerd met de lock
of SyncLock
taalconstructie), de Interlocked klasse en de AutoResetEvent klasse. Er worden twee internal
(in C#) of Friend
(in Visual Basic)-klassen gedefinieerd en UnSyncResource
SyncResource
, die respectievelijk gesynchroniseerde en niet-gesynchroniseerde toegang tot een resource bieden. Om ervoor te zorgen dat het voorbeeld het verschil illustreert tussen de gesynchroniseerde en niet-gesynchroniseerde toegang (wat het geval kan zijn als elke methode-aanroep snel wordt voltooid), bevat de methode een willekeurige vertraging: voor threads waarvan Thread.ManagedThreadId de eigenschap zelfs is, roept Thread.Sleep de methode aan om een vertraging van 2000 milliseconden in te voeren. Houd er rekening mee dat, omdat de SyncResource
klasse niet openbaar is, geen van de clientcode een vergrendeling op de gesynchroniseerde resource accepteert. De interne klasse zelf neemt de vergrendeling. Dit voorkomt dat schadelijke code een vergrendeling op een openbaar object in beslag neemt.
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.
In het voorbeeld wordt een variabele gedefinieerd, numOps
waarmee het aantal threads wordt gedefinieerd dat toegang probeert te krijgen tot de resource. De toepassingsthread roept de ThreadPool.QueueUserWorkItem(WaitCallback) methode aan voor gesynchroniseerde en niet-gesynchroniseerde toegang vijf keer per keer. De ThreadPool.QueueUserWorkItem(WaitCallback) methode heeft één parameter, een gemachtigde die geen parameters accepteert en geen waarde retourneert. Voor gesynchroniseerde toegang wordt de SyncUpdateResource
methode aangeroepen. Voor niet-gesynchroniseerde toegang wordt de UnSyncUpdateResource
methode aangeroepen. Na elke set methodeaanroepen roept de toepassingsthread de methode AutoResetEvent.WaitOne aan, zodat deze wordt geblokkeerd totdat het AutoResetEvent exemplaar wordt gesignaleerd.
Elke aanroep naar de SyncUpdateResource
methode roept de interne SyncResource.Access
methode aan en roept vervolgens de methode aan om de numOps
Interlocked.Decrement teller te verlagen. De Interlocked.Decrement methode wordt gebruikt om de teller te verlagen, omdat anders niet zeker is dat een tweede thread toegang heeft tot de waarde voordat de afgebroken waarde van een eerste thread is opgeslagen in de variabele. Wanneer de laatste gesynchroniseerde werkrolthread het teller naar nul afzet, wat aangeeft dat alle gesynchroniseerde threads de toegang tot de resource hebben voltooid, roept de SyncUpdateResource
methode de EventWaitHandle.Set methode aan, die de hoofdthread aangeeft om door te gaan met de uitvoering.
Elke aanroep naar de UnSyncUpdateResource
methode roept de interne UnSyncResource.Access
methode aan en roept vervolgens de methode aan om de numOps
Interlocked.Decrement teller te verlagen. Opnieuw wordt de Interlocked.Decrement methode gebruikt om de teller te verlagen om ervoor te zorgen dat een tweede thread geen toegang heeft tot de waarde voordat de afgebroken waarde van een eerste thread aan de variabele is toegewezen. Wanneer de laatste niet-gesynchroniseerde werkrolthread de teller afzet op nul, wat aangeeft dat er geen niet-gesynchroniseerde threads meer toegang nodig hebben tot de resource, roept de UnSyncUpdateResource
methode de EventWaitHandle.Set methode aan, die de hoofdthread aangeeft om door te gaan met de uitvoering.
Zoals in de uitvoer van het voorbeeld wordt weergegeven, zorgt gesynchroniseerde toegang ervoor dat de aanroepende thread de beveiligde resource verlaat voordat een andere thread er toegang toe heeft; elke thread wacht op de voorafgaande thread. Aan de andere kant, zonder de vergrendeling, wordt de UnSyncResource.Access
methode aangeroepen in de volgorde waarin threads deze bereiken.