Подтвердить что ты не робот

Почему существует CancellationTokenRegistration и почему он реализует IDisposable

Я видел код, который использует Cancellation.Register с предложением using в результатах CancellationTokenRegistration:

using (CancellationTokenRegistration ctr = token.Register(() => wc.CancelAsync()))
{
    await wc.DownloadStringAsync(new Uri("http://www.hamster.com"));
}

Я понял, что вы должны убедиться, что вы Dispose a IDisposable, но почему он даже реализует IDisposable? какие ресурсы он должен выпустить? Единственные методы, которые он считает равенством.

Что произойдет, если вы не Dispose этого? что вы просачиваетесь?

4b9b3361

Ответ 1

Этот шаблон является удобным способом убедиться, что CancellationTokenRegistration.Unregister() вызывается автоматически. Он часто используется Стивеном Тубом в его Parallel Programming with.NET блоге, например. здесь.

Я понимаю, что вы должны убедиться, что у вас есть IDisposable, но почему он даже реализует IDisposable? какие ресурсы он должен выпуск? Единственные методы, которые он считает равенством.

IMO, лучший ответ на это можно найти в .NET 4 Cancellation Framework от Microsoft Майк Лидделл:

Когда обратный вызов зарегистрирован на CancellationToken, текущий поток ExecutionContext захватывается так, что обратный вызов будет запущен с тем же самым контекстом безопасности. Захват может быть запрошен текущий контекст синхронизации потока через перегрузку ct.Register(), если требуется. Обратные вызовы обычно сохраняется, а затем запускается при отмене запроса, но если обратный вызов регистрируется после того, как была запрошена аннулирование, обратный вызов будет немедленно запускать текущий поток или через Send() на текущий SynchronizationContext если применимо.

Когда обратный вызов зарегистрирован на CancellationToken, возвращается объект имеет значение CancellationTokenRegistration. Это тип легкой структуры то есть IDiposable, и удаление этого объекта регистрации вызывает обратный вызов для снятия с учета. Гарантируется, что после Dispose() вернулся, зарегистрированный обратный вызов не является и впоследствии не начнется. Следствием этого является то, что CancellationTokenRegistration.Dispose() должен блокироваться, если обратный вызов в настоящее время выполняется. Следовательно, все зарегистрированные обратные вызовы должны быть быстрыми и не блокировать какую-либо значительную продолжительность.

Другим релевантным документом Майка Лидделла является "Использование поддержки аннулирования в .NET Framework 4" (UsingCancellationinNET4.pdf).

Обновлено, это проверяется здесь, в исходном источнике.

Также важно отметить, что обратный вызов отмены регистрируется с помощью CancellationTokenSource, а не с CancellationToken. Таким образом, если CancellationTokenRegistration.Dispose() не имеет корректной области действия, регистрация будет оставаться активной для жизни родительского объекта CancellationTokenSource. Это может привести к неожиданному обратному вызову, когда объем операции async завершен, например:

async Task TestAsync(WebClient wc, CancellationToken token)
{
    token.Register(() => wc.CancelAsync());
    await wc.DownloadStringAsync(new Uri("http://www.hamster.com"));
}

// CancellationTokenSource.Cancel() may still get called later,
// in which case wc.CancelAsync() will be invoked too

Таким образом, важно объединить одноразовое CancellationTokenRegistration с помощью using (или вызвать CancellationTokenRegistration.Dispose() явно с помощью try/finally).

Ответ 2

почему он даже реализует IDisposable? какие ресурсы он должен выпустить?

IDisposable часто используется для вещей, совершенно не связанных с выпуском ресурсов; это удобный способ гарантировать, что что-то будет сделано в конце блока using, даже если возникает исключение. Некоторые (а не я) утверждают, что это злоупотребление шаблоном Dispose.

В случае CancellationToken.Register "что-то" является отмена обратного вызова.

Обратите внимание, что в коде, опубликованном в вашем вопросе, использование блока using на CancellationTokenRegistration почти наверняка является ошибкой: поскольку wc.DownloadStringAsync немедленно возвращается, обратный вызов будет немедленно незарегистрирован, перед тем, как операция будет отменена, поэтому wc.CancelAsync никогда не будет вызываться, даже если сигнализируется CancellationToken. Было бы разумно, если бы был вызван вызов wc.DownloadStringAsync, потому что в этом случае конец блока using не был бы достигнут до завершения wc.DownloadStringAsync. (EDIT: это не так больше, поскольку вопрос был отредактирован)

Что произойдет, если вы не избавитесь от него? что вы просачиваетесь?

В этом случае происходит то, что обратный вызов не является незарегистрированным. Это, вероятно, не имеет особого значения, поскольку на него ссылается только токен отмены, а так как CancellationToken - это тип значения, который обычно хранится только в стеке, эта ссылка исчезает, когда она выходит за пределы области видимости. Поэтому в большинстве случаев он ничего не будет пропускать, но может быть, если вы храните CancellationToken где-то в куче. EDIT: на самом деле, это не так; см. сообщение Noseratio для объяснения