14

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

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

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

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

+2

Это утилизация злоупотреблений, вместо этого он должен иметь метод Unregister(). –

+6

@HansPassant: будет ли это «злоупотребление» или нет, зависит от того, выполняется ли просмотр «IDisposable.Dispose» как существующего с целью очистки ресурсов или для обеспечения того, чтобы что-то, что нужно сделать, выполняется (очистка ресурсов является доминирующим типом «необходимого действия», но не единственным). – supercat

+1

@HansPassant - Он также широко используется в asp.net MVC. Я не думаю, что это злоупотребление, если авторы карьеры делают это сами. –

ответ

14

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

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

ИМО, лучший ответ на этот вопрос можно найти в .NET 4 Cancellation Framework сообщение от Microsoft Майк Лиддел:

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

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

Другим важным документом Майка Лидделла является "Using Cancellation Support in .NET Framework 4" (UsingCancellationinNET4.pdf).

Обновлено, это поддающееся проверке here in the Reference Source.

Важно также отметить, что обратный вызов отмены регистрации зарегистрирован на 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).

+0

Не будет ли захват ExecutionContext в «обычном» лямбда-выражении? и это не нужно удалять ... – i3arnon

+1

@ l3arnon, не было бы. «ExecutionContext» захватывается платформой только при наличии потенциального переключателя потоков. Это был бы большой объем накладных расходов, если бы он был захвачен/восстановлен для любого «обычного» лямбда-обратного вызова. Подробнее [здесь] (http://blogs.msdn.com/b/pfxteam/archive/2012/06/15/executioncontext-vs-synchronizationcontext.aspx). – Noseratio

+0

Что касается обновления, вы уверены, что это правда? Что регистрация связана с CTS (через всю свою жизнь), а не только с CT? – i3arnon

3

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

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

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

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

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

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

+0

* «Это, вероятно, не имеет особого значения, потому что на него ссылается только токен отмены ... ссылка исчезнет, ​​когда она исчезнет из области видимости» * - фактически, обратный вызов отмены зарегистрирован в «CancellationTokenSource», поэтому он будет оставаться активным, если 'CancellationToken' выходит за рамки. Это может быть проблемой позже, если аннулирование по-прежнему запрашивается в том же источнике, [подробнее] (http://stackoverflow.com/a/21653382/1768303). – Noseratio

+0

@Noseratio, интересно, я не знал этого ... –

Смежные вопросы