有没有适当的方法来取消异步任务?

Is there a proper way to cancel an asynchronous task?

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

这是一些草稿。

我的入口点运行两个异步任务。第一个任务做一些 'long' 工作,第二个任务取消它。

入口点:

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

returns可取消任务的方法:

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 方法?

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