Считается ли допустимым не вызывать Dispose () для объекта TPL Task? - PullRequest
115 голосов
/ 17 сентября 2010

Я хочу запустить задачу для выполнения в фоновом потоке. Я не хочу ждать завершения задач.

В .net 3.5 я бы сделал это:

ThreadPool.QueueUserWorkItem(d => { DoSomething(); });

В .net 4 TPL - рекомендуемый способ. Рекомендуемая общая схема: 1006 *

Task.Factory.StartNew(() => { DoSomething(); });

Однако метод StartNew() возвращает объект Task, который реализует IDisposable. это кажется, упускается из виду людьми, которые рекомендуют этот шаблон. Документация MSDN по методу Task.Dispose() гласит:

«Всегда вызывайте Dispose, прежде чем выпускать последнюю ссылку на задание.»

Вы не можете вызывать dispose для задачи до тех пор, пока она не будет завершена, так что ожидание основного потока и вызов dispose в первую очередь лишат смысла выполнение в фоновом потоке. Также, похоже, нет завершенного / завершенного события, которое можно было бы использовать для очистки.

Страница MSDN в классе Task не комментирует это, а книга "Pro C # 2010 ..." рекомендует тот же шаблон и не комментирует удаление задачи.

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

Итак, мои вопросы:

  • Допустимо ли в этом случае не звонить Dispose() на Task класс? И если да, то почему и есть ли риски / последствия?
  • Есть какая-нибудь документация, которая обсуждает это?
  • Или есть подходящий способ избавления от объекта Task, который я пропустил?
  • Или есть другой способ ведения огня и забыть задачи с TPL?

Ответы [ 3 ]

100 голосов
/ 17 сентября 2010

Об этом идет обсуждение на форумах MSDN .

Стивен Тауб, член команды Microsoft pfx, может сказать:

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

Обновление (октябрь 2012 г.)
Стивен Туб опубликовал блог под названием Нужно ли распоряжаться Задачами? , в котором дается более подробная информация и объясняются улучшения в .Net 4.5.

В итоге: вам не нужно избавляться от Task объектов в 99% случаев.

Есть две основные причины избавиться от объекта: своевременно освободить неуправляемые ресурсы,детерминистическим способом и избежать затрат на запуск финализатора объекта.Ни один из них не применяется к Task большую часть времени:

  1. Начиная с .Net 4.5, единственный раз, когда Task выделяет внутренний дескриптор ожидания (единственный неуправляемый ресурс в Taskобъект) - это когда вы явно используете IAsyncResult.AsyncWaitHandle из Task и
  2. Сам объект Task не имеет финализатора;дескриптор сам заключен в объект с финализатором, поэтому, если он не выделен, финализатор не может быть запущен.
14 голосов
/ 17 сентября 2010

Это та же проблема, что и с классом Thread.Он использует 5 дескрипторов операционной системы, но не реализует IDisposable.Хорошее решение оригинальных дизайнеров, конечно, есть несколько разумных способов вызвать метод Dispose ().Сначала вам нужно вызвать Join ().

Класс Task добавляет один дескриптор к этому, внутреннее событие ручного сброса.Какой самый дешевый ресурс операционной системы есть.Конечно, его метод Dispose () может освободить только один дескриптор события, а не 5 дескрипторов, которые потребляет Thread. Да, не беспокойтесь .

Имейте в виду, что вам должно быть интересно свойство IsFaults задачи.Это довольно уродливая тема, о ней вы можете прочитать в статье MSDN Library .Как только вы справитесь с этим должным образом, в вашем коде также должно появиться хорошее место для выполнения задач.

0 голосов
/ 09 декабря 2010

Мне бы очень хотелось, чтобы кто-то оценил технику, показанную в этом посте: Typesafe, вызывающий асинхронный вызов типа fire-and-забудьте в C #

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

public static void FireAndForget<T>(this Action<T> act,T arg1)
{
    var tsk = Task.Factory.StartNew( ()=> act(arg1),
                                     TaskCreationOptions.LongRunning);
    tsk.ContinueWith(cnt => cnt.Dispose());
}
...