Отмена. Часть 6: Связывание. Продолжение
Начало
Варианты использования
Внешний токен и внутренний источник отмены могут на самом деле представлять что угодно; связанные токены отмены полезны, когда вам нужно отменить код, если «A или B».
Но наиболее распространённый вариант использования — когда внешний токен представляет запрос отмены конечного пользователя, а внутренний токен - тайм-аут. Например, когда бизнес-логика представляет собой код типа «тайм-аут и повторная попытка», а также позволяет конечному пользователю отменить все повторные попытки одним нажатием кнопки.
Одним из естественных мест, где используется этот тип кода, является Polly. Polly позволит передать внешний токен, который находится под вашим контролем. Затем он передаёт другой токен отмены вашему делегату выполнения; этот внутренний токен контролируется Polly. Конвейеры Polly (например, тайм-аут) могут отменить внутренний токен для отмены вашего делегата. Естественно, если ваш код отменяет внешний токен, переданный Polly, это также перейдет во внутренний токен. То есть, они связаны:
async Task ExecuteRetryTimeoutAsync(
CancellationToken ct)
{
var pipeline = new ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromSeconds(10))
.Build();
await pipeline.ExecuteAsync(async token =>
{
/* ваш код тут */
}, ct);
}
ExecuteRetryTimeoutAsync принимает внешний токен ct и передаёт его Polly. Затем Polly создаёт связанный внутренний токен (который включает поведение конвейера, такое как тайм-аут), и передаёт внутренний токен (token) вашему делегату.
Делегаты, которые вы передаёте Polly, должны следить за токеном, который они получают от Polly, а не за какими-либо другими! Это может оказаться ловушкой, когда вы добавляете конвейеры Polly в существующий код, например, при добавлении тайм-аутов в этот код:
async Task ExecuteAsync(CancellationToken ct)
{
for (int i = 0; i != 10; ++i)
await Task.Delay(1000, ct);
}
Частая ошибка – забыть обновить использованный токен:
async Task ExecuteWithTimeoutAsync(
CancellationToken ct)
{
var pipeline = new ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromSeconds(10))
.Build();
await pipeline.ExecuteAsync(async token =>
{
// ПЛОХОЙ КОД!!!
for (int i = 0; i != 10; ++i)
await Task.Delay(1000, ct);
}, ct);
}
Здесь делегат по-прежнему следит за внешним токеном отмены ct, а должен следить за токеном token:
async Task ExecuteWithTimeoutAsync(
CancellationToken ct)
{
var pipeline = new ResiliencePipelineBuilder()
.AddTimeout(TimeSpan.FromSeconds(10))
.Build();
await pipeline.ExecuteAsync(async token =>
{
for (int i = 0; i != 10; ++i)
await Task.Delay(1000, token);
}, ct);
}
Окончание следует…
Источник: https://blog.stephencleary.com/2024/10/cancellation-6-linking.html