有没有正确的方法来取消异步任务?

时间:2017-08-29 18:49:48

标签: c# .net asynchronous task cancellationtokensource

我遇到了如何正确取消异步任务的问题。

这是一些草案。

我的入口点运行两个异步任务。第一项任务是“长期”工作,第二项是取消它。

切入点:

private static void Main()
{
    var ctc = new CancellationTokenSource();

    var cancellable = ExecuteLongCancellableMethod(ctc.Token);

    var cancelationTask = Task.Run(() =>
    {
        Thread.Sleep(2000);

        Console.WriteLine("[Before cancellation]");

        ctc.Cancel();
    });

    try
    {
        Task.WaitAll(cancellable, cancelationTask);
    }
    catch (Exception e)
    {
        Console.WriteLine($"An exception occurred with type {e.GetType().Name}");
    }
}

返回可取消任务的方法:

private static Task ExecuteLongCancellableMethod(CancellationToken token)
{
    return Task.Run(() =>
    {
        token.ThrowIfCancellationRequested();

        Console.WriteLine("1st"); 
        Thread.Sleep(1000);

        Console.WriteLine("2nd");
        Thread.Sleep(1000);

        Console.WriteLine("3rd");
        Thread.Sleep(1000);

        Console.WriteLine("4th");
        Thread.Sleep(1000);

        Console.WriteLine("[Completed]");

    }, token);  
}   

我的目的是在取消预订后立即停止写'1st','2nd','3rd'。 但我得到以下结果:

1st
2nd
3rd
[Before cancellation]
4th
[Completed]

由于显而易见的原因,我没有得到在请求取消时抛出的异常。所以我尝试重写方法如下:

private static Task ExecuteLongCancellableAdvancedMethod(CancellationToken token)
{
    return Task.Run(() =>
    {
        var actions = new List<Action>
        {
            () => Console.WriteLine("1st"),
            () => Console.WriteLine("2nd"),
            () => Console.WriteLine("3rd"),
            () => Console.WriteLine("4th"),
            () => Console.WriteLine("[Completed]")
        };

        foreach (var action in actions)
        {
            token.ThrowIfCancellationRequested();

            action.Invoke();

            Thread.Sleep(1000);
        }

    }, token);
}

现在我得到了我想要的东西:

1st
2nd
[Before cancellation]
3rd
An exception occurred with type AggregateException

但是我想创建一个Action委托集合并循环遍历它并不是处理我的问题最方便的方法。

那么这样做的正确方法是什么?为什么我需要将取消令牌作为第二个参数传递给Task.Run方法?

1 个答案:

答案 0 :(得分:2)

Task不会取消其自我,您可以检测取消请求并干净地中止您的工作。这就是token.ThrowIfCancellationRequested();所做的。

您应该将这些检查放在代码中,可以干净地停止执行的地方,或者回滚到安全状态。

在你的第二个例子中,每次循环迭代都会调用一次,它运行正常。第一个示例一开始只调用一次。如果该点没有取消令牌,则任务将完成,就像您所看到的那样。

如果您将其更改为如此,您还会看到您期望的结果。

return Task.Run(() =>
{
    token.ThrowIfCancellationRequested();
    Console.WriteLine("1st"); 
    Thread.Sleep(1000);

    token.ThrowIfCancellationRequested();
    Console.WriteLine("2nd");
    Thread.Sleep(1000);

    token.ThrowIfCancellationRequested();
    Console.WriteLine("3rd");
    Thread.Sleep(1000);

    token.ThrowIfCancellationRequested();
    Console.WriteLine("4th");
    Thread.Sleep(1000);

    Console.WriteLine("[Completed]");

}, token);