Annullamento delle attività

Le classi System.Threading.Tasks.Task e System.Threading.Tasks.Task<TResult> supportano l'annullamento mediante i token di annullamento. Per altre informazioni, vedere Annullamento in thread gestiti. Nelle classi Task l'annullamento comporta la cooperazione tra il delegato dell'utente, che rappresenta un'operazione annullabile, e il codice che ha richiesto l'annullamento. Un annullamento riuscito prevede una chiamata al metodo CancellationTokenSource.Cancel da parte del codice di richiesta, nonché l'interruzione tempestiva dell'operazione da parte del delegato dell'utente. L'operazione può essere interrotta tramite una di queste opzioni:

  • Mediante restituzione del delegato. In molti scenari, questa opzione è sufficiente. Tuttavia, un'istanza dell'attività annullata in questo modo passa allo stato TaskStatus.RanToCompletion, non allo stato TaskStatus.Canceled.

  • Generando un oggetto OperationCanceledException e passandogli il token in cui è stato richiesto l'annullamento. Il modo preferito per eseguire queste operazioni è tramite il metodo ThrowIfCancellationRequested. Un'attività annullata in questo modo esegue la transizione allo stato Canceled. Il codice chiamante può usare tale stato per verificare che l'attività abbia risposto alla richiesta di annullamento.

Nell'esempio seguente viene mostrato il modello di base relativo all'annullamento dell’attività che genera l'eccezione:

Nota

Il token viene passato al delegato dell’utente e all'istanza dell'attività.

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

class Program
{
    static async Task Main()
    {
        var tokenSource2 = new CancellationTokenSource();
        CancellationToken ct = tokenSource2.Token;

        var task = Task.Run(() =>
        {
            // Were we already canceled?
            ct.ThrowIfCancellationRequested();

            bool moreToDo = true;
            while (moreToDo)
            {
                // Poll on this property if you have to do
                // other cleanup before throwing.
                if (ct.IsCancellationRequested)
                {
                    // Clean up here, then...
                    ct.ThrowIfCancellationRequested();
                }
            }
        }, tokenSource2.Token); // Pass same token to Task.Run.

        tokenSource2.Cancel();

        // Just continue on this thread, or await with try-catch:
        try
        {
            await task;
        }
        catch (OperationCanceledException e)
        {
            Console.WriteLine($"{nameof(OperationCanceledException)} thrown with message: {e.Message}");
        }
        finally
        {
            tokenSource2.Dispose();
        }

        Console.ReadKey();
    }
}
Imports System.Threading
Imports System.Threading.Tasks

Module Test
    Sub Main()
        Dim tokenSource2 As New CancellationTokenSource()
        Dim ct As CancellationToken = tokenSource2.Token

        Dim t2 = Task.Factory.StartNew(Sub()
                                           ' Were we already canceled?
                                           ct.ThrowIfCancellationRequested()

                                           Dim moreToDo As Boolean = True
                                           While moreToDo = True
                                               ' Poll on this property if you have to do
                                               ' other cleanup before throwing.
                                               If ct.IsCancellationRequested Then

                                                   ' Clean up here, then...
                                                   ct.ThrowIfCancellationRequested()
                                               End If

                                           End While
                                       End Sub _
        , tokenSource2.Token) ' Pass same token to StartNew.

        ' Cancel the task.
        tokenSource2.Cancel()

        ' Just continue on this thread, or Wait/WaitAll with try-catch:
        Try
            t2.Wait()

        Catch e As AggregateException

            For Each item In e.InnerExceptions
                Console.WriteLine(e.Message & " " & item.Message)
            Next
        Finally
            tokenSource2.Dispose()
        End Try

        Console.ReadKey()
    End Sub
End Module

Per un esempio più esaustivo, vedere Procedura: Annullare un'attività e i relativi figli.

Quando un'istanza dell'attività rileva un oggetto OperationCanceledException generato dal codice utente, confronta il token dell'eccezione con il token associato (cioè quello passato all'API che ha creato l'attività). Se i due token sono uguali e la proprietà IsCancellationRequested del token restituisce true, questo viene interpretato dall'attività come una conferma di annullamento e passa allo stato Canceled. Se per attendere l’attività non si usa un metodo Wait o un metodo WaitAll, l’attività si limita a impostare il proprio stato su Canceled.

Se si resta in attesa di un'attività che effettua la transizione allo stato Canceled, viene generata un'eccezione System.Threading.Tasks.TaskCanceledException (di cui viene eseguito il wrapping in un'eccezione AggregateException). Questa eccezione non indica una situazione di errore, bensì l'esito positivo di un annullamento. Di conseguenza la proprietà Exception dell'attività restituisce null.

Se la proprietà IsCancellationRequested del token restituisce false o se il token dell'eccezione non corrisponde a quello dell'attività, l'oggetto OperationCanceledException viene trattato come un'eccezione normale, il che comporta la transizione dell'attività allo stato Faulted. Anche la presenza di altre eccezioni comporterà la transizione dell'attività allo stato Faulted. È possibile ottenere lo stato dell'attività completata nella proprietà Status .

È possibile che un'attività continui a elaborare alcuni elementi dopo la richiesta di annullamento.

Vedi anche