【问题标题】:Wait for a canceled task to complete before continuing execution等待取消的任务完成后再继续执行
【发布时间】:2014-08-28 03:51:20
【问题描述】:

我有以下代码,其中可以取消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

如果我将cancellationToken 传递给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);
    }
}

我准备了一把小提琴here

This question 的标题与我的非常相似,但不幸的是,接受的答案与我的情况无关。

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

【问题讨论】:

    标签: c# design-patterns asynchronous task cancellation


    【解决方案1】:

    如果设置了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);
            }
        }
    

    您可能会发现这篇文章很有用How do I cancel non-cancelable async operations?

    【讨论】:

    • 如果我不调用t.Wait(),那么Console.WriteLine("2"); 将在TaskCancellationTestNotWorking() 返回后异步执行。这是我需要避免的(如果我正在写文件而不是写“2”,我需要确保TaskCancellationTestNotWorking() 在文件写入操作完成之前不会返回)。
    • 如果任务处于Canceled 状态,IsCompleted 将返回 true,尽管它可能仍在执行代码。
    • 你已经调用了Wait(),它抛出了一个异常,你为什么还要调用它?
    • 你也可以用Thread.Sleep(2000)替换Task.Delay(2000).Wait();
    • 我知道Wait()不起作用,但目的是真正等待任务完成(即使被取消后)再返回,避免方法返回后执行鬼操作.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-25
    • 2019-11-01
    • 2021-05-06
    • 2021-05-21
    相关资源
    最近更新 更多