Когда утилизировать CancellationTokenSource? - PullRequest
135 голосов
/ 05 августа 2011

Класс CancellationTokenSource одноразовый. Быстрый просмотр Reflector подтверждает использование KernelEvent, (весьма вероятного) неуправляемого ресурса. Так как CancellationTokenSource не имеет финализатора, если мы не распорядимся им, сборщик мусора не сделает этого.

С другой стороны, если вы посмотрите на примеры, перечисленные в статье MSDN Отмена в управляемых потоках , только один фрагмент кода избавится от токена.

Как правильно распорядиться этим в коде?

  1. Вы не можете переносить код, начинающий параллельное задание с using, если вы его не ждете. И имеет смысл иметь отмену, только если вы не ждете.
  2. Конечно, вы можете добавить ContinueWith к задаче с помощью вызова Dispose, но так ли это?
  3. А как насчет отменяемых запросов PLINQ, которые не синхронизируются обратно, а просто что-то делают в конце? Допустим, .ForAll(x => Console.Write(x))?
  4. Это многоразовое использование? Можно ли использовать один и тот же токен для нескольких вызовов, а затем утилизировать его вместе с компонентом хоста, скажем, для управления пользовательским интерфейсом?

Поскольку в нем нет чего-то вроде Reset метода для очистки полей IsCancelRequested и Token, я бы предположил, что его нельзя использовать повторно, поэтому каждый раз, когда вы запускаете задачу (или запрос PLINQ), вы должны создать новенький. Это правда? Если да, мой вопрос заключается в том, какова правильная и рекомендуемая стратегия для работы с Dispose на этих многих CancellationTokenSource экземплярах?

Ответы [ 6 ]

62 голосов
/ 18 сентября 2012

Говоря о том, действительно ли необходимо вызывать Dispose на CancellationTokenSource ... У меня была утечка памяти в моем проекте, и оказалось, что CancellationTokenSource была проблемой.

В моем проекте есть служба, которая постоянно читает базу данных и запускает различные задачи, и я передавал связанные токены отмены моим работникам, поэтому даже после того, как они закончили обработку данных, токены отмены не были утилизированы, что вызвало утечка памяти.

MSDN Отмена в управляемых потоках гласит:

Обратите внимание, что вы должны вызвать Dispose на связанном источнике токена, когда закончите с ним. Для более полного примера см. Как: прослушивать несколько запросов отмены .

Я использовал ContinueWith в своей реализации.

33 голосов
/ 16 июня 2015

Я не думаю, что любой из текущих ответов был удовлетворительным. После исследования я нашел ответ Стивена Туба ( ссылка ):

Это зависит. В .NET 4 CTS.Dispose служил двум основным целям. Если CancellationToken's WaitHandle был доступен (таким образом, лениво выделив его), Dispose избавится от этой ручки. Кроме того, если CTS был создан с помощью метода CreateLinkedTokenSource, Dispose отсоединит CTS от токенов, с которыми он был связан. В .NET 4.5, У избавления есть дополнительная цель, которая состоит в том, если CTS использует Таймер под крышками (например, CancelAfter был вызван), таймер будет Ликвидировано.

Очень редко можно использовать CancellationToken.WaitHandle, поэтому очистка после него, как правило, не является хорошей причиной для использования утилизации. Если, однако, вы создаете свой CTS с помощью CreateLinkedTokenSource, или если вы используете функцию таймера CTS, это может быть более эффективным использовать Dispose.

Жирная часть, я думаю, является важной частью. Он использует «более эффектный», что делает его немного расплывчатым. Я интерпретирую это как означающее, что в таких ситуациях следует выполнять вызов Dispose, в противном случае использование Dispose не требуется.

25 голосов
/ 05 августа 2011

Я посмотрел в ILSpy для CancellationTokenSource, но я могу найти только m_KernelEvent, который на самом деле ManualResetEvent, который является классом-оболочкой для объекта WaitHandle. Это должно быть правильно обработано ГХ.

18 голосов
/ 26 августа 2013

Вы всегда должны распоряжаться CancellationTokenSource.

Как распоряжаться ею, зависит именно от сценария.Вы предлагаете несколько разных сценариев.

  1. using работает только тогда, когда вы используете CancellationTokenSource на какой-то параллельной работе, которую вы ожидаете.Если это ваш senario, то отлично, это самый простой метод.

  2. При использовании заданий используйте задание ContinueWith, как вы указали, чтобы утилизировать CancellationTokenSource.

  3. Для plinq вы можете использовать using, так как вы запускаете его параллельно, но ожидаете завершения для всех параллельно работающих рабочих.

  4. Для интерфейса пользователя:Вы можете создать новый CancellationTokenSource для каждой отменяемой операции, которая не привязана к одному триггеру отмены.Сохраните List<IDisposable> и добавьте каждый источник в список, утилизируя их при удалении компонента.

  5. Для потоков создайте новый поток, который объединит все рабочие потоки и закроет его.единственный источник, когда все рабочие потоки закончили.См. CancellationTokenSource, Когда утилизировать?

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

14 голосов
/ 06 июля 2014

Этот ответ все еще встречается в поиске в Google, и я считаю, что проголосовавший ответ не дает полной истории.После просмотра исходного кода для CancellationTokenSource (CTS) и CancellationToken (CT) я считаю, что для большинства случаев использования подходит следующая последовательность кода:

if (cancelTokenSource != null)
{
    cancelTokenSource.Cancel();
    cancelTokenSource.Dispose();
    cancelTokenSource = null;
}

Упомянутое выше внутреннее поле m_kernelHandle является объектом синхронизации, поддерживающим свойство WaitHandle в классах CTS и CT.Он создается только при доступе к этому свойству.Таким образом, если вы не используете WaitHandle для какой-либо синхронизации потоков старой школы в вашем Task вызове dispose не будет иметь никакого эффекта.

Конечно, если вы используете , вы должны делать то, что предложено другими ответами выше, и откладывать вызов Dispose до тех пор, пока не завершатся какие-либо операции WaitHandle, использующие дескриптор,, как описано в документации Windows API для WaitHandle , результаты не определены.

3 голосов
/ 07 июля 2018

Прошло много времени с тех пор, как я спросил это и получил много полезных ответов, но я столкнулся с интересной проблемой, связанной с этим, и подумал, что я опубликую это здесь как еще один ответ:

Вам следует позвонитьCancellationTokenSource.Dispose() только тогда, когда вы уверены, что никто не собирается пытаться получить свойство Token CTS.В противном случае вы должны не назвать это, потому что это раса.Например, см. Здесь:

https://github.com/aspnet/AspNetKatana/issues/108

В исправлении этой проблемы код, который ранее делал cts.Cancel(); cts.Dispose();, был отредактирован так, чтобы просто делать cts.Cancel();, потому что кому-то так не повезло, чтобы попытатьсячтобы получить токен отмены для наблюдения за состоянием отмены после Dispose был вызван, к сожалению, также потребуется обработать ObjectDisposedException - в дополнение к OperationCanceledException, который они планировали.

Еще одно ключевое замечание, относящееся к этому исправлению, сделано Tratcher: «Утилизация требуется только для токенов, которые не будут отменены, так как отмена делает все ту же очистку».то есть просто делать Cancel() вместо утилизации - это действительно хорошо!

...