如果其中一个异步方法抛出异常,取消所有异步方法

4

我正在使用取消令牌,并希望了解其工作原理。我有两个异步方法(在我的示例中是两个,但理论上我可以有 100 个)。

如果其中一个方法抛出异常,我想取消所有异步方法的工作。

我的想法是,在调用所有方法的异常中取消令牌。当令牌被取消时,我期望其他方法停止工作,但实际上并没有发生。

using System;
using System.Threading;
using System.Threading.Tasks;

namespace CancelationTest
{
    class Program
    {
        static void Main(string[] args)
        {
            new Test();
            Console.ReadKey();
        }
    }

    public class Test
    {
        public Test()
        {
            Task.Run(() => MainAsync());
        }

        public static async Task MainAsync()
        {
            var cancellationTokenSource = new CancellationTokenSource();
            try
            {
                var firstTask = FirstAsync(cancellationTokenSource.Token);
                var secondTask = SecondAsync(cancellationTokenSource.Token);
                Thread.Sleep(50);
                Console.WriteLine("Begin");
                await secondTask;
                Console.WriteLine("hello");
                await firstTask;
                Console.WriteLine("world");
                Console.ReadKey();
            }
            catch (OperationCanceledException e)
            {
                Console.WriteLine("Main OperationCanceledException cancel");
            }
            catch (Exception e)
            {
                Console.WriteLine("Main Exception + Cancel");
                cancellationTokenSource.Cancel();
            }
        }

        public static async Task FirstAsync(CancellationToken c)
        {
            c.ThrowIfCancellationRequested();
            await Task.Delay(1000, c);
            Console.WriteLine("Exception in first call");
            throw new NotImplementedException("Exception in first call");
        }

        public static async Task SecondAsync(CancellationToken c)
        {
            c.ThrowIfCancellationRequested();
            await Task.Delay(15000, c);
            Console.WriteLine("SecondAsync is finished");
        }
    }
}

第二种方法即使第一种方法抛出异常也会完成工作并延迟任务15秒。
结果是什么:
开始 第一次调用中的异常 SecondAsync已完成 你好 Main Exception + Cancel
我期望secondAsync停止延迟并抛出OperationCanceledException。
我期望这个结果:
开始 第一次调用中的异常 主要异常+取消 主要OperationCanceledException取消
我在哪里犯了错误? 为什么SecondAsync方法完全执行而不抛出异常?如果我改变SecondAsync和FirstAsync的顺序,那么当标记被取消时,第二种方法将停止延迟并抛出异常。

5
取消操作是合作的:您的任务必须定期检查令牌,这需要一个循环。 - H H
@HenkHolterman 在传递 CancellationTokenTask.Delay 时,如果令牌被触发,延迟是否应该被取消? - user4003407
@PetSerAl - 是的,我忽略了那一点。虽然文档对 Delay 何时取消有点含糊不清,但我认为在这里可能仍需要 15 秒钟。 - H H
2
@Raskolnikov 取消操作并不能像你期望的那样“订阅令牌取消事件”。ThrowIfCancellationRequested 仅仅检查是否已经请求了取消操作,如果是,则抛出异常,否则不执行任何操作。它不会在稍后被取消时抛出异常。 - Fabjan
您还可以注册委托,以在请求取消时运行。 - Crowcoder
显示剩余3条评论
3个回答

4
因为您代码的相关部分是:

由于您代码的相关部分是:

try
{
   ...
   await secondTask;
   await firstTask;
}
catch(...)
{
    source.Cancel();
}

现在虽然firstTask已经启动并且抛出了异常,但是它会在secondTask之后被等待。直到任务被等待时,异常才会在调用者处抛出。因此,在secondTask完成后才会执行 catch 子句。Cancel()的执行时间太晚了。
如果您想中断第二个任务,则必须执行以下操作:
1.将源传递到 FirstAsync 中,并在那里调用 Cancel()。有点丑陋。
2.更改等待结构。我认为您的示例有点不自然。使用 Parallel.Invoke() 或类似的东西就会变得很自然。

它完整而简洁地回答了这个问题。请查看代码。 - H H
我的意思是答案太简单了,如果您能多解释一下'为什么其他方法停止工作'就更好了...也许我错了,但我认为楼主想找到答案的原因是SecondAsync方法已经完整执行并且在ThrowIfCancellationRequested中没有抛出异常。 - Fabjan
我不知道OP对ThrowIfCancellationRequested有什么看法,但在这里并不重要。 - H H
他们不需要是异步的。你说得对,这样做不太好。我在处理取消操作。 - H H
你为什么认为将源代码传递给FirstAsync会很丑陋?如果传递令牌不丑陋,那么传递源代码而不是令牌为什么会丑陋呢? - Raskolnikov
显示剩余7条评论

0

你的 CancellationTokenSource 需要在可被调用方法访问到的作用域内,并且需要调用 CancellationTokenSource.Cancel() 来取消使用该源的所有操作。

你也可以调用 CancellationTokenSource.CancelAfter(TimeSpan) 在延迟后取消它。


它适用于两种方法的范围,Main 方法调用 Cancel()。 - H H

0

必须手动检查取消操作。您只需在延迟之前调用c.ThrowIfCancellationRequested();一次即可。因此,一旦延迟开始,您将不会注册任何取消操作。

相反,您应该使用适合您的应用程序的循环时间循环检查取消操作:

        public static async Task SecondAsync(CancellationToken c)
        {
            for (int i = 0; i<150; i++)
            {
                c.ThrowIfCancellationRequested();
                await Task.Delay(100, c);
            }
            c.ThrowIfCancellationRequested();
            Console.WriteLine("SecondAsync is finished");
        }

在生产代码中,如果您有一个非常线性的工作流程,您可能希望每隔一段时间插入取消检查。
编辑: 另外,正如Henk Holterman在他们的答案中指出的那样,在您的代码中,通过await firstAsync传播的异常在await secondAsnc完成之前无法捕获。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接