Search code examples
c#async-awaitcancellationcancellation-token

Should I prefer to throw or return when cancellation is requested?


Consider the two following approaches to handling cancellation via CancellationToken:

public async Task DoAllAvailableWork(CancellationToken cancelToken)
{
    foreach (var job in GetAllAvailableWork())
    {
        await job.Process();

        if (cancelToken.IsCancellationRequested())
            return;
    }
}

public async Task DoAllAvailableWork(CancellationToken cancelToken)
{
    foreach (var job in GetAllAvailableWork())
    {
        await job.Process();

        cancelToken.ThrowIfCancellationRequested();
    }
}

In this case job.Process() is doing some atomic work that should not or cannot be stopped once it begins, so it does not accept a CancellationToken.

Is there any reason to prefer one of these approaches over the other? If yes, which approach should be preferred?

Checking IsCancellationRequested() and returning feels cleaner to me, in the sense that throwing implies something has gone wrong, and cancellation is a case that we explicitly planned to handle (that's why we accept the CancellationToken). On the other hand, the caller can't necessarily know which approach we'll take, so they have to set up a try/catch for OperationCancelledException regardless of which option we choose.


Solution

  • ThrowIfCancellationRequested was designed for task continuations. Throwing stops the whole continuation chain (with the exception of cancel-handling continuations). An unhandled OperationCancelledException in a task will cancel the Task's cancellation token as well (if any).

    In your case, there's no Task. You are free to define your own interface. But keep in mind that whoever calls your method should also have a way of seeing if your operation was cancelled or not - after all, something else must have caused the cancellation if all you have are synchronous methods. Depending on what the cancellation means, returning an error, an empty result, false or throwing an exception (but probably not OperationCancelledException anyway!) might each make their sense - you need to tailor it to the kind of interface you're making.