Share via


Útmutató: Több lemondási kérés figyelése

Ez a példa bemutatja, hogyan hallgathat meg egyszerre két lemondási jogkivonatot, hogy megszakíthassa a műveletet, ha bármelyik jogkivonat kéri.

Feljegyzés

Ha a "Just My Code" engedélyezve van, a Visual Studio bizonyos esetekben megszakad a kivételt jelző sorban, és megjelenik egy hibaüzenet, amely azt jelzi, hogy "a felhasználói kód által nem kezelt kivétel". Ez a hiba jóindulatú. A folytatáshoz nyomja le az F5 billentyűt, és tekintse meg az alábbi példákban bemutatott kivételkezelési viselkedést. Ha meg szeretné akadályozni, hogy a Visual Studio feltörje az első hibát, törölje a jelet a "Just My Code" (Csak saját kód) jelölőnégyzetből az Eszközök, Beállítások, Hibakeresés, Általános területen.

Példa

Az alábbi példában a CreateLinkedTokenSource metódus két jogkivonat egy jogkivonatba való csatlakoztatására szolgál. Ez lehetővé teszi, hogy a jogkivonat olyan metódusok számára legyen átadva, amelyek csak egy lemondási jogkivonatot vesznek fel argumentumként. A példa egy gyakori forgatókönyvet mutat be, amelyben egy metódusnak az osztályon kívülről átadott jogkivonatot és az osztályon belül létrehozott jogkivonatot is figyelembe kell vennie.

using System;
using System.Threading;
using System.Threading.Tasks;

class LinkedTokenSourceDemo
{
    static void Main()
    {
        WorkerWithTimer worker = new WorkerWithTimer();
        CancellationTokenSource cts = new CancellationTokenSource();

        // Task for UI thread, so we can call Task.Wait wait on the main thread.
        Task.Run(() =>
        {
            Console.WriteLine("Press 'c' to cancel within 3 seconds after work begins.");
            Console.WriteLine("Or let the task time out by doing nothing.");
            if (Console.ReadKey(true).KeyChar == 'c')
                cts.Cancel();
        });

        // Let the user read the UI message.
        Thread.Sleep(1000);

        // Start the worker task.
        Task task = Task.Run(() => worker.DoWork(cts.Token), cts.Token);

        try
        {
            task.Wait(cts.Token);
        }
        catch (OperationCanceledException e)
        {
            if (e.CancellationToken == cts.Token)
                Console.WriteLine("Canceled from UI thread throwing OCE.");
        }
        catch (AggregateException ae)
        {
            Console.WriteLine("AggregateException caught: " + ae.InnerException);
            foreach (var inner in ae.InnerExceptions)
            {
                Console.WriteLine(inner.Message + inner.Source);
            }
        }

        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
        cts.Dispose();
    }
}

class WorkerWithTimer
{
    CancellationTokenSource internalTokenSource = new CancellationTokenSource();
    CancellationToken internalToken;
    CancellationToken externalToken;
    Timer timer;

    public WorkerWithTimer()
    {
        // A toy cancellation trigger that times out after 3 seconds
        // if the user does not press 'c'.
        timer = new Timer(new TimerCallback(CancelAfterTimeout), null, 3000, 3000);
    }

    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();
                }
            }
        }
    }

    private void DoWorkInternal(CancellationToken token)
    {
        for (int i = 0; i < 1000; i++)
        {
            if (token.IsCancellationRequested)
            {
                // We need to dispose the timer if cancellation
                // was requested by the external token.
                timer.Dispose();

                // Throw the exception.
                token.ThrowIfCancellationRequested();
            }

            // Simulating work.
            Thread.SpinWait(7500000);
            Console.Write("working... ");
        }
    }

    public void CancelAfterTimeout(object? state)
    {
        Console.WriteLine("\r\nTimer fired.");
        internalTokenSource.Cancel();
        timer.Dispose();
    }
}
Imports System.Threading
Imports System.Threading.Tasks

Class LinkedTokenSourceDemo
    Shared Sub Main()
        Dim worker As New WorkerWithTimer()
        Dim cts As New CancellationTokenSource()

        ' Task for UI thread, so we can call Task.Wait wait on the main thread.
        Task.Run(Sub()
                     Console.WriteLine("Press 'c' to cancel within 3 seconds after work begins.")
                     Console.WriteLine("Or let the task time out by doing nothing.")
                     If Console.ReadKey(True).KeyChar = "c"c Then
                         cts.Cancel()
                     End If
                 End Sub)
        ' Let the user read the UI message.
        Thread.Sleep(1000)

        ' Start the worker task.
        Dim t As Task = Task.Run(Sub() worker.DoWork(cts.Token), cts.Token)
        Try
            t.Wait()
        Catch ae As AggregateException
            For Each inner In ae.InnerExceptions
                Console.WriteLine(inner.Message)
            Next
        End Try

        Console.WriteLine("Press any key to exit.")
        Console.ReadKey()
        cts.Dispose()
    End Sub
End Class

Class WorkerWithTimer
    Dim internalTokenSource As CancellationTokenSource
    Dim token As CancellationToken
    Dim timer As Timer

    Public Sub New()
        internalTokenSource = New CancellationTokenSource()
        token = internalTokenSource.Token

        ' A toy cancellation trigger that times out after 3 seconds
        ' if the user does not press 'c'.
        timer = New Timer(New TimerCallback(AddressOf CancelAfterTimeout), Nothing, 3000, 3000)
    End Sub

    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

    Private Sub DoWorkInternal(ByVal token As CancellationToken)
        For i As Integer = 0 To 1000
            If token.IsCancellationRequested Then
                ' We need to dispose the timer if cancellation
                ' was requested by the external token.
                timer.Dispose()

                ' Output for demonstration purposes.
                Console.WriteLine(vbCrLf + "Cancelling per request.")

                ' Throw the exception.
                token.ThrowIfCancellationRequested()
            End If

            ' Simulating work.
            Thread.SpinWait(7500000)
            Console.Write("working... ")
        Next
    End Sub

    Public Sub CancelAfterTimeout(ByVal state As Object)
        Console.WriteLine(vbCrLf + "Timer fired.")
        internalTokenSource.Cancel()
        timer.Dispose()
    End Sub
End Class

Amikor a csatolt jogkivonat egy OperationCanceledException, a kivételnek átadott jogkivonat a csatolt jogkivonat, nem pedig a megelőző jogkivonatok egyike. A jogkivonatok törlésének megállapításához ellenőrizze közvetlenül a jogkivonatok állapotát.

Ebben a példában soha nem szabad dobni, AggregateException de ez azért van itt, mert valós forgatókönyvekben a tevékenységmeghatalmazotttól kapott kivételeken kívül OperationCanceledException minden más kivételt egy AggregateException.

Lásd még