Megosztás a következőn keresztül:


Lemondás felügyelt szálakban

A .NET-keretrendszer 4-től 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 könnyűsúlyú objektumnak nevezett lemondási tokenre épül. 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 ezután továbbadhatják a token másolatát más műveleteknek. 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 törlési értesítést az egyes törlési tokeneknek.

  • Adja át a CancellationTokenSource.Token tulajdonság által visszaadott jogkivonatot minden olyan tevékenységnek vagy szálnak, amely a megszakítá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. Feltétlenül meg kell hívnia a CancellationTokenSource.Dispose metódust, miután befejezte a lemondási tokénforrás használatát, hogy felszabadíthassa a nem felügyelt erőforrásokat.

Az alábbi ábrán a jogkivonat forrása és a jogkivonat összes másolata közötti kapcsolat látható.

A CancellationTokenSource és a megszakítási tokenek

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ésen alapul, és nem kényszeríti a hallgatóra. A hallgató határozza meg, hogyan lehet elegánsan megszakítani 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érelmet kérvényező objektum egyetlen metódushívással kiadja a jogkivonat összes másolatának törlési kérését.

  • A hallgató egyszerre több tokent is meghallgathat, ha eggyé kapcsolja őket egy csatolt token.

  • 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 hallgatók lekérdezéssel, visszahívás-regisztrációval vagy várakozó fogópontokon 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ípus neve 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ási mechanizmussal vagy várakozási ponttal figyelik a jogkivonat IsCancellationRequested tulajdonságának értékét.
OperationCanceledException A kivétel konstruktorának túlterhelései egy CancellationToken paramétert fogadnak el. A figyelők opcionálisan kiadhatják ezt a kivételt a törlés forrásának ellenőrzéséhez, és a többi fél értesítése céljából, hogy válaszolt egy törlési kérelemre.

A lemondási modell több típusban van integrálva a .NET-be. A legfontosabbak a System.Threading.Tasks.Parallel, System.Threading.Tasks.Task, System.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.

Kódpélda

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 nevű token tulajdonságának értékét. Amikor az érték true lesz, a figyelő bármilyen számára megfelelő módon leállhat. Ebben a példában a metódus egyszerűen kilép, ami sok esetben elégséges.

Megjegyzés:

A példa a QueueUserWorkItem metódust használja annak bemutatására, hogy a kooperatív lemondási keretrendszer kompatibilis a régebbi API-kkal. Az előnyben részesített System.Threading.Tasks.Task típus használatát bemutató példáért tekintse meg a Feladat és gyermekeinek megszakítása című útmutatót.

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 Example1
    Public Sub Main1()
        ' 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 tokennek egy "megszakítható műveletre" kell hivatkoznia, függetlenül attól, hogyan van az a művelet implementálva a programban. IsCancellationRequested tulajdonságának a tokenen való beállítása után true-re nem állítható vissza false-re. Ezért a lemondási jogkivonatok a lemondás után nem használhatók fel újra.

Ha az objektum lemondási mechanizmusára van szüksége, az operáció lemondási mechanizmusát használhatja az CancellationToken.Register metódus meghívásával, 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 {id} Cancel callback");
        // 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 ExampleOb1
    Public Sub MainOb1()
        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 OperationCanceledException dobandóvá teszi. A könytárkód észlelheti ezt a kivételt a felhasználói delegált szálon, és megvizsgálhatja a kivétel tokenjét annak megállapítására, hogy a kivétel együttműködő lemondást vagy más kivételes helyzetet jelent-e.

Az Task osztály így kezeli a OperationCanceledException. További információ: Tevékenység lemondása.

Figyelés lekérdezéssel

A hosszú ideig futó ciklusok vagy rekurziók esetében megszakítási kéréseket figyelhet a CancellationToken.IsCancellationRequested tulajdonság értékének rendszeres lekérdezésével. Ha az értéke true, a metódusnak amilyen gyorsan csak lehet, tisztítania és leállnia kell. 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 row As Integer = 0 To rect.rows - 1
            ' Simulating work.
            Thread.SpinWait(5000)
            Console.Write("0',1' ", col, row)
        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.

Visszahívási értesítés regisztrálásával történő figyelés

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érelemet a Register metódussal.

using System;
using System.Net.Http;
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)
    {
        var client = new HttpClient();

        token.Register(() =>
        {
            client.CancelPendingRequests();
            Console.WriteLine("Request cancelled!");
        });

        Console.WriteLine("Starting request.");
        client.GetStringAsync(new Uri("http://www.contoso.com"));
    }
}
Imports System.Net
Imports System.Net.Http
Imports System.Threading

Class Example4
    Private Shared Sub Main4()
        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 client As New HttpClient()

        token.Register(Sub()
                           client.CancelPendingRequests()
                           Console.WriteLine("Request cancelled!")
                       End Sub)

        Console.WriteLine("Starting request.")
        client.GetStringAsync(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ásnak gyorsnak kell lennie, mert szinkron módon hívódik meg, ezért a Cancel hívása nem tér vissza, amíg a visszahívás be nem fejeződik.

  • Ha a visszahívás futása közben hívja meg a Dispose-t, és tart egy olyan zárolást, amelyre a visszahívás vár, akkor a program holtpontra juthat. Amikor Dispose visszatér, felszabadíthatja a visszahíváshoz szükséges erőforrásokat.

  • A visszahívások során nem szabad manuális szálkezelést vagy SynchronizationContext használatot végezni. 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álvezérlést végez egy visszahívásban, az holtpontot okozhat.

A teljesebb példáért lásd: Visszahívások regisztrálása lemondási kérelmekhez.

Várakozáskezelő használatával történő figyelés

Amikor egy lemondható művelet blokkolódhat, miközben egy szinkronizálási prmitíven várakozik, mint például egy System.Threading.ManualResetEvent vagy System.Threading.Semaphore, a CancellationToken.WaitHandle tulajdonság használatával lehetővé teheti, hogy a művelet mind az eseményre, mind a lemondási kérésre várakozzon. A lemondási token várakozási objektuma jelezni fog egy lemondási kérelemre adott válaszként, és a metódus a WaitAny metódus visszatérési értékét használhatja annak meghatározására, hogy a lemondási token jelezte-e. A művelet ezután egyszerűen kiléphet, vagy szükség szerint dobhat egy OperationCanceledException-t.

// 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 támogatják mind a törlési keretrendszert a Wait metódusaikban. Átadhatja a CancellationToken metódusnak, és amikor a megszakítást kérik, az esemény aktiválódik, é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 token egyidejű figyelése

Bizonyos esetekben előfordulhat, hogy a hallgatónak egyszerre több törlési tokent is meg kell hallgatnia. Előfordulhat például, hogy egy megszakítható műveletnek egy belső megszakító jelzőt kell figyelnie a külsőleg átadott jelző mellett, amely a metódus egyik paramétereként szerepel. 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

Vegye figyelembe, hogy meg kell hívnia a kapcsolódó token forrást a Dispose akkor, ha már végzett vele. Útmutató: Több lemondási kérelem figyelése egy teljesebb példaért.

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önyvtári kód visszavonja a felhasználói kódot, és a felhasználói kód együttműködő módon mondja le a könyvtári kódot. 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önyvtári kód felhasználói kódot hív meg, a könyvtári kódnak az OperationCanceledException(externalToken) kivételt kooperatív törlésként kell értelmeznie, és nem feltétlenül hibakivételként.

  • A felhasználói meghatalmazottaknak próbáljanak válaszolni a könyvtárkódból érkező lemondási kérelmekre időben.

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.

Lásd még