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

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

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

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

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


person i3arnon    schedule 26.01.2014    source источник
comment
Это злоупотребление удалением, вместо него должен быть метод Unregister().   -  person Hans Passant    schedule 26.01.2014
comment
@HansPassant: Злоупотребление это или нет, зависит от того, рассматривает ли кто-то IDisposable.Dispose как существующий с целью очистки ресурсов или с целью обеспечения того, чтобы что-то, что необходимо сделать, было выполнено (очистка ресурсов является доминирующим типом необходимых действий). , но не единственный).   -  person supercat    schedule 09.02.2014
comment
@HansPassant - он также широко используется в asp.net MVC. Я не считаю злоупотреблением, если авторы фреймворка делают это сами.   -  person Erik Funkenbusch    schedule 10.02.2014


Ответы (2)


Этот шаблон — удобный способ убедиться, что CancellationTokenRegistration.Unregister() вызывается автоматически. Его часто использует Стивен Тауб в своих сообщениях в блоге Параллельное программирование с .NET, например. здесь.

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

ИМО, лучший ответ на этот вопрос можно найти в . 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 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).

person noseratio    schedule 09.02.2014
comment
Разве ExecutionContext не будет также захвачен в обычном лямбда-выражении? и это не надо утилизировать... - person i3arnon; 04.05.2014
comment
@ l3arnon, нет, не будет. ExecutionContext захватывается платформой только тогда, когда задействовано потенциальное переключение потока. Было бы довольно много накладных расходов, если бы он был захвачен/восстановлен для любого обычного обратного вызова лямбда. Подробнее здесь. - person noseratio; 04.05.2014
comment
Что касается обновления, вы уверены, что это правда? Что регистрация связана с CTS (на протяжении всего срока его службы), а не только с CT? - person i3arnon; 04.05.2014
comment
@ l3arnon, я уверен и считаю это логичным: отмена запрашивается через CTS, а не через один из его токенов. Если вам нужны дополнительные сведения, проверьте это для m_source.InternalRegister. - person noseratio; 04.05.2014

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

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

В случае CancellationToken.Register «что-то» — это отмена регистрации обратного вызова.

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

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

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

person Thomas Levesque    schedule 04.05.2014
comment
Возможно, это не имеет большого значения, потому что на него ссылается только токен отмены... ссылка исчезнет, ​​когда выйдет за пределы области видимости - на самом деле, обратный вызов отмены зарегистрирован с помощью CancellationTokenSource, поэтому он будет оставаться активным, когда CancellationToken выходит за рамки. Это может стать проблемой позже, если отмена по-прежнему запрашивается в том же источнике, подробнее. - person noseratio; 04.05.2014