首页 > 解决方案 > 我应该何时调用 CancellationToken.ThrowIfCancellationRequested?

问题描述

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

我注意到,在调用函数OperationCanceledException时请求取消时,这些函数都没有抛出异常,所以我的应用程序只是继续执行,直到我ThrowIfCancellationRequested()稍后在代码中调用其他地方。我是否应该ThrowIfCancellationRequested()在调用每个函数后手动调用以确保任务尽快停止,或者我应该什么时候调用ThrowIfCancellationRequested()自己的代码?

标签: c#multithreadingsynchronizationcancellationtokensourcecancellation-token

解决方案


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

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

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

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

推荐阅读