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

Когда удалять CancellationTokenSource?

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

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

Каков правильный способ избавиться от него в коде?

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

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

4b9b3361

Ответ 1

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

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

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

Обратите внимание, что вы должны вызывать Dispose в источнике связанных токенов, когда вы закончите с ним. Более подробный пример см. В разделе Практическое руководство. Прослушайте несколько запросов на аннулирование.

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

Ответ 2

Я не думал, что какие-либо из текущих ответов были удовлетворительными. После исследования я нашел этот ответ от Stephen Toub (ссылка):

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

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

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

Ответ 3

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

Ответ 4

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

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

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

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

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

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

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

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

Ответ 5

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

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

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

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

Ответ 6

Создайте новое приложение Windows Forms из шаблона проекта. Отбросьте кнопку в форме и дважды щелкните ее. Сделайте это так:

    private void button1_Click(object sender, EventArgs e) {
        var t = new System.Threading.Thread(() => { });
        t.Start();
    }

Нажмите Ctrl + F5, чтобы запустить его. Запустите + Запустить, TaskMgr.exe, вкладка Процессы. Открыть + Выбрать столбцы и отметить "Ручки". Обратите внимание на значение этого столбца для процесса WindowsFormsApplication1.exe, когда вы нажимаете кнопку несколько раз.

Класс Thread не имеет метода Dispose().

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


Читайте больше о мудрости, пытаясь избавиться от труднодоступных объектов в этом сообщении в блоге от Stephen Toub.

Ответ 7

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

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

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

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

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