我应该什么时候打电话给 CancellationToken.ThrowIfCancellationRequested?

When should I call CancellationToken.ThrowIfCancellationRequested?

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

我注意到 none 这些函数在调用函数时请求取消时抛出 OperationCanceledException,所以我的应用程序只是继续执行,直到我在某处调用 ThrowIfCancellationRequested()其他在我的代码中。我是否应该在调用每个函数后手动调用 ThrowIfCancellationRequested() 以确保任务尽快停止,或者我应该在我自己的代码中调用 ThrowIfCancellationRequested() 的确切时间?

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

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

另请注意,您是对的 - 接受令牌的标准框架函数 不会 抛出取消异常 - 它们只会 return 提前,所以你有自行检查取消。

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