Lemondás felügyelt szálakban
A 4. .NET-keretrendszer kezdve a .NET egységes modellt használ az aszinkron vagy hosszú ideig futó szinkron műveletek kooperatív törléséhez. Ez a modell egy egyszerűsített, úgynevezett lemondási jogkivonaton alapul. Az az objektum, amely egy vagy több megszakítható műveletet hív meg, például új szálak vagy feladatok létrehozásával, átadja a jogkivonatot minden műveletnek. Az egyes műveletek más műveleteknek is átadhatják a jogkivonat másolatát. Egy későbbi időpontban a jogkivonatot létrehozó objektum kérheti, hogy a műveletek leállítsák azt, amit csinálnak. A lemondási kérelmet csak a kérelmező objektum bocsáthatja ki, és minden figyelő felelős azért, hogy a kérést észrevehesse, és megfelelő és időben válaszoljon rá.
A szövetkezeti lemondási modell megvalósításának általános mintája:
Példányosít egy CancellationTokenSource objektumot, amely kezeli és elküldi a lemondási értesítést az egyes lemondási jogkivonatoknak.
Adja át a CancellationTokenSource.Token tulajdonság által visszaadott jogkivonatot minden olyan tevékenységnek vagy szálnak, amely a lemondást figyeli.
Adjon meg egy mechanizmust minden tevékenységhez vagy szálhoz a lemondásra való reagáláshoz.
A lemondásról szóló értesítés megadásához hívja meg a CancellationTokenSource.Cancel metódust.
Fontos
Az CancellationTokenSource osztály megvalósítja az IDisposable felületet. Mindenképpen hívja meg a CancellationTokenSource.Dispose metódust, ha befejezte a lemondási jogkivonat forrásának használatát a nem felügyelt erőforrások felszabadításához.
Az alábbi ábrán a jogkivonat forrása és a jogkivonat összes másolata közötti kapcsolat látható.
A kooperatív lemondási modell megkönnyíti a lemondást támogató alkalmazások és kódtárak létrehozását, és a következő funkciókat támogatja:
A lemondás együttműködő, és nem kényszeríti a figyelőre. A figyelő határozza meg, hogyan lehet elegánsan leállni egy lemondási kérelemre válaszul.
A kérés eltér a figyeléstől. A lemondható műveletet meghívó objektumok szabályozhatják, hogy mikor (ha van ilyen) lemondást kérnek.
A kérést kérő objektum egyetlen metódushívással kiadja a jogkivonat összes másolatának lemondási kérését.
A figyelők egyszerre több jogkivonatot is meghallgathatnak, ha összekapcsolják őket egy csatolt jogkivonattal.
A felhasználói kód észlelheti és megválaszolhatja a kódtárkód lemondási kéréseit, a kódtárkód pedig a felhasználói kódból érkező lemondási kérelmeket és azokra reagálhat.
A figyelők lekérdezéssel, visszahívás-regisztrációval vagy várakozási fogópontokra való várakozással értesülhetnek a lemondási kérelmekről.
Lemondási típusok
A lemondási keretrendszer a kapcsolódó típusok halmazaként van implementálva, amelyek az alábbi táblázatban szerepelnek.
Típusnév | Leírás |
---|---|
CancellationTokenSource | Olyan objektum, amely létrehoz egy lemondási jogkivonatot, és a jogkivonat minden példányára vonatkozóan kiadja a lemondási kérelmet. |
CancellationToken | Egy vagy több figyelőnek átadott egyszerű értéktípus, jellemzően metódusparaméterként. A figyelők lekérdezéssel, visszahívással vagy várakozási fogóponttal figyelik a jogkivonat tulajdonságának értékét IsCancellationRequested . |
OperationCanceledException | A kivétel konstruktorának túlterhelései paraméterként fogadnak el.CancellationToken A figyelők igény szerint ki is adhatják ezt a kivételt a lemondás forrásának ellenőrzéséhez, és értesíthetik a többi felhasználót arról, hogy válaszolt egy lemondási kérelemre. |
A lemondási modell több típusban van integrálva a .NET-be. A legfontosabbak a System.Threading.Tasks.Parallelkövetkezők, System.Threading.Tasks.TaskSystem.Threading.Tasks.Task<TResult> és System.Linq.ParallelEnumerable. Javasoljuk, hogy ezt a kooperatív lemondási modellt használja az összes új kódtárhoz és alkalmazáskódhoz.
Példa kódra
Az alábbi példában a kérelmező objektum létrehoz egy CancellationTokenSource objektumot, majd átadja a tulajdonságát Token a megszakítható műveletnek. A kérést fogadó művelet lekérdezéssel figyeli a IsCancellationRequested jogkivonat tulajdonságának értékét. Amikor az érték válik true
, a figyelő bármilyen módon leállhat. Ebben a példában a metódus csak kilép, ami sok esetben szükséges.
Feljegyzés
A példa a módszert használja annak QueueUserWorkItem bizonyítására, hogy a kooperatív lemondási keretrendszer kompatibilis az örökölt API-kkal. Az előnyben részesített System.Threading.Tasks.Task típust használó példáért tekintse meg a Feladat és gyermekei megszakítása című témakört.
using System;
using System.Threading;
public class Example
{
public static void Main()
{
// Create the token source.
CancellationTokenSource cts = new CancellationTokenSource();
// Pass the token to the cancelable operation.
ThreadPool.QueueUserWorkItem(new WaitCallback(DoSomeWork), cts.Token);
Thread.Sleep(2500);
// Request cancellation.
cts.Cancel();
Console.WriteLine("Cancellation set in token source...");
Thread.Sleep(2500);
// Cancellation should have happened, so call Dispose.
cts.Dispose();
}
// Thread 2: The listener
static void DoSomeWork(object? obj)
{
if (obj is null)
return;
CancellationToken token = (CancellationToken)obj;
for (int i = 0; i < 100000; i++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("In iteration {0}, cancellation has been requested...",
i + 1);
// Perform cleanup if necessary.
//...
// Terminate the operation.
break;
}
// Simulate some work.
Thread.SpinWait(500000);
}
}
}
// The example displays output like the following:
// Cancellation set in token source...
// In iteration 1430, cancellation has been requested...
Imports System.Threading
Module Example
Public Sub Main()
' Create the token source.
Dim cts As New CancellationTokenSource()
' Pass the token to the cancelable operation.
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf DoSomeWork), cts.Token)
Thread.Sleep(2500)
' Request cancellation by setting a flag on the token.
cts.Cancel()
Console.WriteLine("Cancellation set in token source...")
Thread.Sleep(2500)
' Cancellation should have happened, so call Dispose.
cts.Dispose()
End Sub
' Thread 2: The listener
Sub DoSomeWork(ByVal obj As Object)
Dim token As CancellationToken = CType(obj, CancellationToken)
For i As Integer = 0 To 1000000
If token.IsCancellationRequested Then
Console.WriteLine("In iteration {0}, cancellation has been requested...",
i + 1)
' Perform cleanup if necessary.
'...
' Terminate the operation.
Exit For
End If
' Simulate some work.
Thread.SpinWait(500000)
Next
End Sub
End Module
' The example displays output like the following:
' Cancellation set in token source...
' In iteration 1430, cancellation has been requested...
Művelettörlés és objektumtörlés
A szövetkezeti lemondási keretrendszerben a lemondás műveletekre vonatkozik, nem objektumokra. A lemondási kérelem azt jelenti, hogy a műveletnek a lehető leghamarabb le kell állnia a szükséges törlés végrehajtása után. Egy lemondási jogkivonatnak egy "megszakítható műveletre" kell hivatkoznia, de ez a művelet implementálható a programban. IsCancellationRequested A jogkivonat tulajdonságának beállítása true
után nem állítható vissza a következőrefalse
: . Ezért a lemondási jogkivonatok a lemondás után nem használhatók fel újra.
Ha objektumlemondási mechanizmust igényel, a metódus meghívásával CancellationToken.Register alapulhat a műveletlemondási mechanizmuson, ahogyan az az alábbi példában is látható.
using System;
using System.Threading;
class CancelableObject
{
public string id;
public CancelableObject(string id)
{
this.id = id;
}
public void Cancel()
{
Console.WriteLine("Object {0} Cancel callback", id);
// Perform object cancellation here.
}
}
public class Example1
{
public static void Main()
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
// User defined Class with its own method for cancellation
var obj1 = new CancelableObject("1");
var obj2 = new CancelableObject("2");
var obj3 = new CancelableObject("3");
// Register the object's cancel method with the token's
// cancellation request.
token.Register(() => obj1.Cancel());
token.Register(() => obj2.Cancel());
token.Register(() => obj3.Cancel());
// Request cancellation on the token.
cts.Cancel();
// Call Dispose when we're done with the CancellationTokenSource.
cts.Dispose();
}
}
// The example displays the following output:
// Object 3 Cancel callback
// Object 2 Cancel callback
// Object 1 Cancel callback
Imports System.Threading
Class CancelableObject
Public id As String
Public Sub New(id As String)
Me.id = id
End Sub
Public Sub Cancel()
Console.WriteLine("Object {0} Cancel callback", id)
' Perform object cancellation here.
End Sub
End Class
Module Example
Public Sub Main()
Dim cts As New CancellationTokenSource()
Dim token As CancellationToken = cts.Token
' User defined Class with its own method for cancellation
Dim obj1 As New CancelableObject("1")
Dim obj2 As New CancelableObject("2")
Dim obj3 As New CancelableObject("3")
' Register the object's cancel method with the token's
' cancellation request.
token.Register(Sub() obj1.Cancel())
token.Register(Sub() obj2.Cancel())
token.Register(Sub() obj3.Cancel())
' Request cancellation on the token.
cts.Cancel()
' Call Dispose when we're done with the CancellationTokenSource.
cts.Dispose()
End Sub
End Module
' The example displays output like the following:
' Object 3 Cancel callback
' Object 2 Cancel callback
' Object 1 Cancel callback
Ha egy objektum egynél több egyidejű megszakítható műveletet támogat, adjon át egy külön jogkivonatot bemenetként az egyes megszakítható műveletekhez. Így az egyik művelet megszakítható anélkül, hogy a többire hatással lenne.
Lemondási kérelmek figyelése és megválaszolása
A felhasználói delegáltban a lemondható művelet megvalósítója határozza meg, hogyan lehet megszüntetni a műveletet egy lemondási kérelemre válaszul. A felhasználói meghatalmazott sok esetben egyszerűen elvégezheti a szükséges törlést, majd azonnal visszatérhet.
Összetettebb esetekben azonban előfordulhat, hogy a felhasználó meghatalmazottjának értesítenie kell a kódtár kódját a lemondásról. Ilyen esetekben a művelet leállításának helyes módja az, ha a meghatalmazott meghívja a ThrowIfCancellationRequested, metódust, ami a dobást okozza OperationCanceledException . A kódtárkód észlelheti ezt a kivételt a felhasználói delegált szálon, és megvizsgálhatja a kivétel jogkivonatát annak megállapításához, hogy a kivétel kooperatív lemondást vagy más kivételes helyzetet jelez-e.
Az Task osztály így kezeli a fogópontokat OperationCanceledException . További információ: Tevékenység lemondása.
Figyelés lekérdezéssel
Az ismétlődő vagy ismétlődő hosszú ideig futó számítások esetében a lemondási kéréseket figyelheti a tulajdonság értékének CancellationToken.IsCancellationRequested rendszeres lekérdezésével. Ha az értéke, true
a metódusnak a lehető leggyorsabban el kell törölnie és leállnia. A lekérdezés optimális gyakorisága az alkalmazás típusától függ. A fejlesztő feladata, hogy meghatározza az adott programhoz tartozó legjobb lekérdezési gyakoriságot. A lekérdezés önmagában nem befolyásolja jelentősen a teljesítményt. Az alábbi példa egy lehetséges lekérdezési módot mutat be.
static void NestedLoops(Rectangle rect, CancellationToken token)
{
for (int col = 0; col < rect.columns && !token.IsCancellationRequested; col++) {
// Assume that we know that the inner loop is very fast.
// Therefore, polling once per column in the outer loop condition
// is sufficient.
for (int row = 0; row < rect.rows; row++) {
// Simulating work.
Thread.SpinWait(5_000);
Console.Write("{0},{1} ", col, row);
}
}
if (token.IsCancellationRequested) {
// Cleanup or undo here if necessary...
Console.WriteLine("\r\nOperation canceled");
Console.WriteLine("Press any key to exit.");
// If using Task:
// token.ThrowIfCancellationRequested();
}
}
Shared Sub NestedLoops(ByVal rect As Rectangle, ByVal token As CancellationToken)
Dim col As Integer
For col = 0 To rect.columns - 1
' Assume that we know that the inner loop is very fast.
' Therefore, polling once per column in the outer loop condition
' is sufficient.
For col As Integer = 0 To rect.rows - 1
' Simulating work.
Thread.SpinWait(5000)
Console.Write("0',1' ", x, y)
Next
Next
If token.IsCancellationRequested = True Then
' Cleanup or undo here if necessary...
Console.WriteLine(vbCrLf + "Operation canceled")
Console.WriteLine("Press any key to exit.")
' If using Task:
' token.ThrowIfCancellationRequested()
End If
End Sub
Egy teljesebb példa : A lemondási kérelmek figyelése lekérdezéssel.
Figyelés visszahívás regisztrálásával
Egyes műveletek olyan módon blokkolhatók, hogy nem tudják időben ellenőrizni a lemondási jogkivonat értékét. Ezekben az esetekben regisztrálhat egy visszahívási metódust, amely feloldja a letiltást a lemondási kérelem fogadásakor.
A Register metódus egy CancellationTokenRegistration kifejezetten erre a célra használt objektumot ad vissza. Az alábbi példa bemutatja, hogyan szakíthat meg aszinkron webkérelmet a Register metódussal.
using System;
using System.Net;
using System.Threading;
class Example4
{
static void Main()
{
CancellationTokenSource cts = new CancellationTokenSource();
StartWebRequest(cts.Token);
// cancellation will cause the web
// request to be cancelled
cts.Cancel();
}
static void StartWebRequest(CancellationToken token)
{
WebClient wc = new WebClient();
wc.DownloadStringCompleted += (s, e) => Console.WriteLine("Request completed.");
// Cancellation on the token will
// call CancelAsync on the WebClient.
token.Register(() =>
{
wc.CancelAsync();
Console.WriteLine("Request cancelled!");
});
Console.WriteLine("Starting request.");
wc.DownloadStringAsync(new Uri("http://www.contoso.com"));
}
}
Imports System.Net
Imports System.Threading
Class Example
Private Shared Sub Main()
Dim cts As New CancellationTokenSource()
StartWebRequest(cts.Token)
' cancellation will cause the web
' request to be cancelled
cts.Cancel()
End Sub
Private Shared Sub StartWebRequest(token As CancellationToken)
Dim wc As New WebClient()
wc.DownloadStringCompleted += Function(s, e) Console.WriteLine("Request completed.")
' Cancellation on the token will
' call CancelAsync on the WebClient.
token.Register(Function()
wc.CancelAsync()
Console.WriteLine("Request cancelled!")
End Function)
Console.WriteLine("Starting request.")
wc.DownloadStringAsync(New Uri("http://www.contoso.com"))
End Sub
End Class
Az CancellationTokenRegistration objektum kezeli a szálszinkronizálást, és gondoskodik arról, hogy a visszahívás egy adott időpontban leálljon.
A rendszer válaszkészségének biztosítása és a holtpontok elkerülése érdekében a visszahívások regisztrálásakor a következő irányelveket kell követni:
A visszahívási módszernek gyorsnak kell lennie, mert szinkron módon van meghívva, ezért a hívás Cancel nem tér vissza, amíg a visszahívás vissza nem tér.
Ha a visszahívás futtatása közben hív Dispose , és a visszahívásra várakozó zárolást tart, a program holtpontra állíthatja. A visszatérés után
Dispose
felszabadíthatja a visszahíváshoz szükséges erőforrásokat.A visszahívások nem végezhetnek manuális szálat vagy SynchronizationContext használatot a visszahívásokban. Ha egy visszahívásnak egy adott szálon kell futnia, használja a System.Threading.CancellationTokenRegistration konstruktort, amely lehetővé teszi annak megadását, hogy a cél syncContext az aktív SynchronizationContext.Current. Ha manuális szálkezelést végez egy visszahívásban, az holtpontot okozhat.
További teljesebb példa : Visszahívások regisztrálása lemondási kérelmekhez.
Figyelés várakozási fogópont használatával
Ha egy lemondható művelet blokkolhatja, amíg egy szinkronizálási primitíven( például egy System.Threading.ManualResetEvent vagy System.Threading.Semaphore) várakozik, a tulajdonság használatával CancellationToken.WaitHandle engedélyezheti, hogy a művelet az eseményre és a lemondási kérelemre is várakozhasson. A lemondási jogkivonat várakozási leírója egy lemondási kérelemre válaszként lesz jelezve, és a metódus a metódus visszatérési értékével WaitAny állapíthatja meg, hogy a lemondási jogkivonat volt-e a jelzés. A művelet ezután egyszerűen kiléphet, vagy szükség szerint dobhat egy OperationCanceledExceptionműveletet.
// Wait on the event if it is not signaled.
int eventThatSignaledIndex =
WaitHandle.WaitAny(new WaitHandle[] { mre, token.WaitHandle },
new TimeSpan(0, 0, 20));
' Wait on the event if it is not signaled.
Dim waitHandles() As WaitHandle = {mre, token.WaitHandle}
Dim eventThatSignaledIndex =
WaitHandle.WaitAny(waitHandles, _
New TimeSpan(0, 0, 20))
System.Threading.ManualResetEventSlim és System.Threading.SemaphoreSlim mindkettő támogatja a lemondási keretrendszert a módszereikben Wait
. Átadhatja a CancellationToken metódusnak, és amikor a lemondást kéri, az esemény felébred, és eldob egy OperationCanceledException.
try
{
// mres is a ManualResetEventSlim
mres.Wait(token);
}
catch (OperationCanceledException)
{
// Throw immediately to be responsive. The
// alternative is to do one more item of work,
// and throw on next iteration, because
// IsCancellationRequested will be true.
Console.WriteLine("The wait operation was canceled.");
throw;
}
Console.Write("Working...");
// Simulating work.
Thread.SpinWait(500000);
Try
' mres is a ManualResetEventSlim
mres.Wait(token)
Catch e As OperationCanceledException
' Throw immediately to be responsive. The
' alternative is to do one more item of work,
' and throw on next iteration, because
' IsCancellationRequested will be true.
Console.WriteLine("Canceled while waiting.")
Throw
End Try
' Simulating work.
Console.Write("Working...")
Thread.SpinWait(500000)
Egy teljesebb példa : Útmutató: Várakozási fogópontokkal rendelkező lemondási kérelmek figyelése.
Több jogkivonat egyidejű hallgatása
Bizonyos esetekben előfordulhat, hogy a figyelőnek egyszerre több lemondási jogkivonatot is meg kell hallgatnia. Előfordulhat például, hogy egy megszakítható műveletnek egy belső lemondási jogkivonatot kell figyelnie a külsőleg átadott jogkivonat mellett egy metódusparaméter argumentumaként. Ehhez hozzon létre egy csatolt jogkivonat-forrást, amely két vagy több jogkivonatot csatlakoztathat egy jogkivonathoz, ahogyan az az alábbi példában látható.
public void DoWork(CancellationToken externalToken)
{
// Create a new token that combines the internal and external tokens.
this.internalToken = internalTokenSource.Token;
this.externalToken = externalToken;
using (CancellationTokenSource linkedCts =
CancellationTokenSource.CreateLinkedTokenSource(internalToken, externalToken))
{
try
{
DoWorkInternal(linkedCts.Token);
}
catch (OperationCanceledException)
{
if (internalToken.IsCancellationRequested)
{
Console.WriteLine("Operation timed out.");
}
else if (externalToken.IsCancellationRequested)
{
Console.WriteLine("Cancelling per user request.");
externalToken.ThrowIfCancellationRequested();
}
}
}
}
Public Sub DoWork(ByVal externalToken As CancellationToken)
' Create a new token that combines the internal and external tokens.
Dim internalToken As CancellationToken = internalTokenSource.Token
Dim linkedCts As CancellationTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(internalToken, externalToken)
Using (linkedCts)
Try
DoWorkInternal(linkedCts.Token)
Catch e As OperationCanceledException
If e.CancellationToken = internalToken Then
Console.WriteLine("Operation timed out.")
ElseIf e.CancellationToken = externalToken Then
Console.WriteLine("Canceled by external token.")
externalToken.ThrowIfCancellationRequested()
End If
End Try
End Using
End Sub
Figyelje meg, hogy a csatolt jogkivonat forrását akkor kell meghívnia Dispose
, ha végzett vele. Egy teljesebb példa : Útmutató: Több lemondási kérelem figyelése.
Együttműködés a kódtárkód és a felhasználói kód között
Az egységes lemondási keretrendszer lehetővé teszi, hogy a kódtárkód visszavonja a felhasználói kódot, a felhasználói kód pedig együttműködési módon mondja le a kódtár kódját. A zökkenőmentes együttműködés az alábbi irányelveket követve minden oldaltól függ:
Ha a kódtárkód megszakítható műveleteket biztosít, a külső lemondási jogkivonatot elfogadó nyilvános metódusokat is meg kell adnia, hogy a felhasználói kód igényelhesse a lemondást.
Ha a kódtárkód felhasználói kódba hív be, a kódtárkódnak az OperationCanceledException(externalToken) függvényt kooperatív lemondásként kell értelmeznie, és nem feltétlenül hibakivételként.
A felhasználói meghatalmazottaknak időben meg kell kísérelni a kódtárkódból érkező lemondási kérelmeket.
System.Threading.Tasks.Task és System.Linq.ParallelEnumerable példák azokra az osztályokra, amelyek az alábbi irányelveket követik. További információ: Feladattörlés és útmutató: PLINQ-lekérdezés lemondása.