Я пытаюсь обеспечить эту семантику для отменяемого фрагмента кода в случае OperationCancelledException:
Если вызывающая сторона является причиной, уважайте ее и (повторно) бросьте
Если мы или какой-либо нижестоящий код является причиной, переведите это на
«недоступный» ответ
К сожалению, вторая часть проблематична.
Если нижестоящий код связывает источник, а затем отменяет + throws, я не могу его обнаружить. Я ожидаю, что мой собственный источник вызовет IsCancellationRequested, но это не так.
Вот некоторый код, иллюстрирующий проблему
public void TestDownstreamCancellation()
{
var cancellationToken = new CancellationTokenSource().Token;
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
try
{
var inner = CancellationTokenSource.CreateLinkedTokenSource(cts.Token);
inner.Cancel();
inner.Token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException e)
{
// All right - we may be seeing this because we have cancelled the internal tasks (they execute
// cancellationToken.ThrowIfCancellationRequested(), which is a rational way to bail out).
// But it could also be because the outer (caller owned) token cancelled, in which case we should re-throw.
if (e.CancellationToken == cancellationToken)
{
// The cause of the exception is that the caller of the composite requested termination, re-throwing!
// Case is separated out for clatiry and (possibly) logging purposes.
throw;
}
else if (cts.Token.IsCancellationRequested)
{
// Check if the cause is actually the passed-in token. If so, we should not translate this exception to a
// ServiceUnavailable response. May be redundant, as the previous if branch should have caught this
cancellationToken.ThrowIfCancellationRequested();
// The cause of the exception is that one of the sub-tasks responded to a cancellation request by throwing
// OperationCanceled. We translate this to ServiceUnavailable. It's always iffy to rely on exception behavior for control
// flow, but in this case it's quite well-defined what's going on, as it's very common for an async task to honor a
// cancellation request by throwing this exception.
// return ServiceUnavailable;
}
else
{
// Something else threw, so raise it again and let the caller deal with it.
throw;
}
}
Я бы ожидал 2-го. срабатывающая ветвь, но это не так - вместо этого мы ударяем 3-ю ветвь и перебрасываем.