Cancelación de tareas

Las clases System.Threading.Tasks.Task y System.Threading.Tasks.Task<TResult> admiten la cancelación mediante tokens de cancelación. Para más información, consulte el tema sobre la cancelación en subprocesos administrados. En las clases Task, la cancelación implica la cooperación entre el delegado de usuario, que representa una operación que se puede cancelar, y el código que ha solicitado la cancelación. Una cancelación correcta implica que el código que la solicita llama al método CancellationTokenSource.Cancel y que el delegado de usuario finaliza la operación a tiempo. Puede finalizar la operación a través de una de estas opciones:

  • Devolver un valor del delegado. En muchos escenarios, esta opción es suficiente. Sin embargo, una instancia de tarea cancelada de esta manera cambia al estado TaskStatus.RanToCompletion, no al estado TaskStatus.Canceled.

  • Producir una excepción OperationCanceledException y pasarle el token en el que se solicitó la cancelación. En este caso, se prefiere usar el método ThrowIfCancellationRequested. Una tarea cancelada de esta manera cambia al estado Cancelado, que sirve al código que realiza la llamada para comprobar que la tarea respondió a su solicitud de cancelación.

En el siguiente ejemplo se muestra el modelo básico para la opción de cancelación de tareas que produce la excepción:

Nota

El token se pasa al delegado de usuario y a la instancia de la tarea.

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

Para consultar un ejemplo más completo, vea Procedimiento para cancelar una tarea y sus elementos secundarios.

Cuando una instancia de tarea observa una excepción OperationCanceledException iniciada desde el código de usuario, compara el token de la excepción con su token asociado (el que se pasó a la API que creó la tarea). Si son iguales y la propiedad IsCancellationRequested del token devuelve true, la tarea lo interpreta como una confirmación de cancelación y pasa al estado Canceled. Si no se usa un método Wait o WaitAll para esperar a la tarea, esta simplemente establece su estado en Canceled.

Si espera en un elemento Task que cambia al estado Canceled, se crea y se inicia una excepción System.Threading.Tasks.TaskCanceledException (encapsulada en la excepción AggregateException). Esta excepción indica la cancelación correcta en lugar de una situación de error. Por consiguiente, la propiedad Exception del elemento Task devuelve null.

Si la propiedad IsCancellationRequested del token devuelve false o si el token de la excepción no coincide con el token de la tarea, OperationCanceledException se trata como una excepción normal, por lo que la tarea cambia al estado Faulted. La presencia de otras excepciones también hará que la tarea pase al estado Faulted. Puede obtener el estado de la tarea completada en la propiedad Status .

Es posible que una tarea continúe procesando algunos elementos una vez solicitada la cancelación.

Vea también