Uso de async/await en C#, parte 3: Cancelar una tarea

Vimos como notificar al usuario de que la operación está corriendo. Pero, ¿qué pasa si el usuario desea cancelar la operación? ¿Qué si queremos cancelarla automáticamente por timeout luego de un tiempo?

Es posible cancelar una tarea, e incluso .NET nos provee metodos para facilitarnos la cancelación por timeout. El codigo se ensucia un poco, pero si necesitamos cancelar, no hay mas remedio.

Esta guia también esta disponible en forma de video, si asi lo preferís:

He aquí el código:

CancellationTokenSource cts;
private async void button1_Click(object sender, EventArgs e)
{
    var progress = new Progress<int>(pct => progressBar1.Value = pct);

    label1.Text = "Operacion Larga";
    try
    {
        cts = new CancellationTokenSource();
        cts.CancelAfter(2000);
        var x = LongOperationAsync(progress, cts.Token);
        await x;
        label1.Text = x.ToString();
    }
    catch (OperationCanceledException ex)
    {
        label1.Text = "Se canceló la operacion" + ex.Message;
    }
    catch (Exception ex)
    {
        label1.Text = ex.Message;
    }
}

async Task<int> LongOperationAsync(IProgress<int> progresser, CancellationToken cToken)
{
    return await Task.Run<int>(() =>
    {

        return LongOperation(progresser, cToken);
    });
}

int LongOperation(IProgress<int> progresser, CancellationToken cToken)
{
    for (int i = 0; i <= 100; i += 10)
    {
        System.Threading.Thread.Sleep(300);
        if(cToken!=null)
            cToken.ThrowIfCancellationRequested();
        if (progresser != null)
            progresser.Report(i);
    }
    return 25;
}

private void button2_Click(object sender, EventArgs e)
{
    if (cts != null && !cts.IsCancellationRequested)
        cts.Cancel();
}
  • En la línea 1 declaramos un CancelationTokenSource, que nos permitirá acceder al hilo desde el hilo principal.
  • En la linea 9 creamos la instancia del hilo.
  • En la linea 10 le decimos al token que debe cancelarse automáticamente pasados 2000 milisegundos.
  • En la linea 15 vemos que la excepción es OperationCanceledException, lo que nos permitirá diferenciar entre una excepción por cancelación, de negocios, del sistema, etc
  • En las lineas 39 y 40 es donde se produce la cancelación. Si el token indica que se debe cancelar, tiramos una excepción que será atrapada en la linea 15.
  • Por ultimo, en la linea 47 se encuentra el método que solicita la cancelación.

Una cuestión importante aquí es que la cancelacion no destruye el hilo, sino que envia una notificación a este. Si la operación larga esta bloqueada, por ejemplo, esperando una red, no nos será posible abortar esta tarea hasta que la misma de timeout. Para que la cancelación funcione, debemos revisar periódicamente el token de cancelación.

Diferenciar entre una cancelación manual y un timeout

Curiosamente, la clase CancelationToken no nos indica el motivo de la cancelación. Como alternativa podemos hacer lo siguiente:

public class CustomCancellationTokenSource: CancellationTokenSource
{
    public bool WasManuallyCanceled { get; private set; }
    public void ManualCancel()
    {
        WasManuallyCanceled = true;
        Cancel();
    }
}

Queda como ejercicio para el lector entender como funciona esta clase y como implementarla.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *