我应该何时调用 CancellationToken.ThrowIfCancellationRequested?

Posted

技术标签:

【中文标题】我应该何时调用 CancellationToken.ThrowIfCancellationRequested?【英文标题】:When should I call CancellationToken.ThrowIfCancellationRequested? 【发布时间】:2021-11-26 18:33:00 【问题描述】:

我开发了一个基于 C# 的 Windows 服务,它在几个不同的任务中运行它的所有逻辑。 为了让服务在停止时正常关闭,我使用了一个 CancellationToken,它被传递给任何接受一个(主要来自我正在使用的第 3 方库)的函数,以便在完成之前中止处理。

我注意到,在调用函数时请求取消时,这些函数都不会抛出 OperationCanceledException,因此我的应用程序只是继续执行,直到我稍后在代码中的其他地方调用 ThrowIfCancellationRequested()。我应该在调用每个函数后手动调用ThrowIfCancellationRequested() 以确保任务尽快停止,还是我应该在我自己的代码中准确地调用ThrowIfCancellationRequested()

【问题讨论】:

【参考方案1】:

是的,您应该在代码中的适当位置手动调用ThrowIfCancellationRequested()(适当的位置由您作为程序员确定)。

考虑以下简单的作业处理函数示例,该函数从队列中读取作业并对其进行处理。 cmets 说明了开发人员在决定是否检查取消时可能会经历的那种思考。

请注意,您是对的 - 接受令牌的标准框架函数不会抛出取消异常 - 它们只会提前返回,因此您必须自己检查取消。

public async Task DoWork(CancellationToken token)

    while(true)
    
        // It is safe to check the token here, as we have not started any work
        token.ThrowIfCancellationRequested();

        var nextJob = GetNextJob();

        // We can check the token here, because we have not 
        // made any changes to the system.
        token.ThrowIfCancellationRequested();

        var jobInfo = httpClient.Get($"job/info/nextJob.Id", token); 
        // We can check the token here, because we have not 
        // made any changes to the system. 
        // Note that HttpClient won't throw an exception
        // if the token is cancelled - it will just return early, 
        // so we must check for cancellation ourselves.
        token.ThrowIfCancellationRequested();

        // The following code is a critical section - we are going to start
        // modifying various databases and things, so don't check for 
        // cancellation until we have done it all.
        ModifySystem1(nextJob);
        ModifySystem2(nextJob);
        ModifySystem3(nextJob);

        // We *could* check for cancellation here as it is safe, but since
        // we have already done all the required work *and* marking a job 
        // as complete is very fast, there is not a lot of point.
        MarkJobAsCompleted(nextJob);
    

最后,您可能不想从您的代码中泄露取消异常,因为它们不是“真正的”异常 - 只要有人停止您的服务,它们就会发生。

您可以使用如下异常过滤器捕获异常:

public async Task DoWork(CancellationToken token)

    try
    
        while(true)
         
            // Do job processing
        
    
    catch (OperationCanceledException e) when (e.CancellationToken == token)
    
        Log.Info("Operation cancelled because service is shutting down.");
    
    catch (Exception e)
    
        Log.Error(e, "Ok - this is actually a real exception. Oh dear.");
    

【讨论】:

以上是关于我应该何时调用 CancellationToken.ThrowIfCancellationRequested?的主要内容,如果未能解决你的问题,请参考以下文章

我是不是应该始终将 CancellationToken 添加到我的控制器操作中?

在键盘事件中使用 CancellationToken 调用 Task.Delay 时出现 TaskCanceledException

我应该何时调用 StateHasChanged 以及 Blazor 何时自动拦截某些更改?

我们应该将 CancellationToken 与 MVC/Web API 控制器一起使用吗?

为 HTML 控件提供建议:我应该何时调用 DispEventUnadvise?

编码自定义 SplitViewController - 我应该何时调用 viewWillAppear、viewDidAppear 等...?