等待取消的任务完成后再继续执行

本文关键字:再继续 执行 取消 任务 等待 | 更新日期: 2023-09-27 18:19:37

我有以下代码,其中可以取消Task,但我基本上需要等待它完成(以确保完整性),然后将OperationCanceledException抛出给调用者。

public static void TaskCancellationTest() {
    try {
        Console.WriteLine("TaskCancellationTest started.");
        var cts = new CancellationTokenSource();
        var t = Task.Run(() => {
            if (cts.Token.IsCancellationRequested) return;
            Console.WriteLine("1");
            Task.Delay(2000).Wait();
            Console.WriteLine("2");
        }).ContinueWith(task => {
            if (cts.Token.IsCancellationRequested) return;
            Console.WriteLine("3");
            Task.Delay(2000).Wait();
            Console.WriteLine("4");
        });
        Task.Run(() => {
            Task.Delay(1000).Wait();
            Console.WriteLine("Cancelling...");
            cts.Cancel();
        });
        t.Wait();
        try {
            cts.Token.ThrowIfCancellationRequested();
        }
        catch (OperationCanceledException) {
            Console.WriteLine("Gracefully canceled.");
        }
        Console.WriteLine("TaskCancellationTest completed.");
    }
    catch (Exception ex) {
        Console.WriteLine("TaskCancellationTest... Failure: " + ex);
    }
}

正如预期的那样,结果是:

1
Cancelling...
2
Gracefully canceled.

它是有效的,但我更愿意将CancellationToken传递给方法,因为我知道这是一个更好的模式。我还希望能够观察方法主体内的令牌,并调用ThrowIfCancellationRequested()中止,而不必等待下一个ContinueWith()。

我在玩下面的替代代码,它也有效,但有什么方法可以引发OperationCanceledException而不是AggregateException吗?

如果我将cancelloken传递给WaitAll()方法,问题是它会在取消令牌后立即抛出OperationCanceledException,而不是等待任务t1t2实际完成(它们将继续在后台运行),然后只抛出异常。

public static void TaskCancellationTest2() {
    try {
        Console.WriteLine("TaskCancellationTest2 started.");
        var cts = new CancellationTokenSource();
        var t1 = Task.Run(() => {
            Console.WriteLine("1");
            Task.Delay(2000).Wait();
            Console.WriteLine("2");
        }, cts.Token);
        var t2 = t1.ContinueWith(task => {
            Console.WriteLine("3");
            Task.Delay(2000).Wait();
            cts.Token.ThrowIfCancellationRequested();
            Console.WriteLine("4");
        }, cts.Token);
        Task.Run(() => {
            Task.Delay(1000).Wait();
            Console.WriteLine("Cancelling...");
            cts.Cancel();
        });
        try {
            try {
                Task.WaitAll(t1, t2);
            }
            catch (AggregateException ae) {
                if (ae.InnerExceptions.Count == 1 && ae.InnerExceptions.Single() is OperationCanceledException) {
                    throw ae.InnerExceptions.Single();
                }
                throw;
            }
        }
        catch (OperationCanceledException) {
            Console.WriteLine("Gracefully canceled.");
        }
        Console.WriteLine("TaskCancellationTest2 completed.");
    }
    catch (Exception ex) {
        Console.WriteLine("TaskCancellationTest2... Failure: " + ex);
    }
}

我在这里准备了一把小提琴。

这个问题的标题与我的非常相似,但不幸的是,公认的答案与我的情况无关。

你知道有什么方法可以实现我想要的,尽可能好地利用CancellationToken吗?

等待取消的任务完成后再继续执行

我认为如果设置了CancellationToken,TPL的设计是为了急切地完成任务。您看到这种行为的部分原因是您正在调用t.Wait(cts.Token)。如果设置了令牌,即使任务尚未运行完成,使用CancellationToken的重载也将停止等待。

ContinueWith相同,如果传入CancellationToken,则一旦设置了该令牌,任务就可以完成。

将代码更改为不带令牌地调用t.Wait()ContinueWith,您将获得所需的行为。

    public static void TaskCancellationTestNotWorking1()
    {
        try
        {
            Console.WriteLine("TaskCancellationTestNotWorking started.");
            var cts = new CancellationTokenSource();
            var t = Task.Run(() =>
            {
                Console.WriteLine("1");
                Thread.Sleep(2000);
                Console.WriteLine("2");
            }, cts.Token).ContinueWith(task =>
            {
                Console.WriteLine("3");
                Thread.Sleep(2000);
                cts.Token.ThrowIfCancellationRequested();
                Console.WriteLine("4");
            });
            Task.Run(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("Cancelling...");
                cts.Cancel();
            }, cts.Token);
            try
            {
                t.Wait();
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("IsCanceled " + t.IsCanceled);
                Console.WriteLine("IsCompleted " + t.IsCompleted);                    
                Console.WriteLine("Gracefully canceled.");
            }
            catch (AggregateException)
            {
                Console.WriteLine("IsCanceled " + t.IsCanceled);
                Console.WriteLine("IsCompleted " + t.IsCompleted);
                Console.WriteLine("Gracefully canceled 1.");
            }
            Console.WriteLine("TaskCancellationTestNotWorking completed.");
        }
        catch (Exception ex)
        {
            Console.WriteLine("TaskCancellationTestNotWorking... Failure: " + ex);
        }
    }

您可能会发现这篇文章很有用。如何取消不可取消的异步操作?