何时应调用CancellationToken.ThrowIfCancellationRequested?

5
我开发了一个基于C#的Windows服务,它在几个不同的任务中运行所有逻辑。为了在停止时使服务优雅地关闭,我使用了一个CancellationToken,将其传递给任何接受它的函数(主要是我正在使用的第三方库),以便在完成之前中止处理。
我注意到,当在调用函数时请求取消时,这些函数都不会抛出OperationCanceledException异常,因此我的应用程序会继续执行,直到我在代码的其他位置调用ThrowIfCancellationRequested()方法。我应该在调用每个这样的函数后手动调用ThrowIfCancellationRequested()方法,以确保任务尽快停止,或者我应该在自己的代码中什么时候调用ThrowIfCancellationRequested()方法?
1个回答

11

是的,在你的代码中,你应该手动调用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.");
    }
}

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