Считается ли приемлемым не вызывать Dispose() в объекте задачи TPL?

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

В .net 3.5 я бы сделал следующее:

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

В .net 4 предлагается TPL. Общим примером, который я видел, является:

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

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

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

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

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

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

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

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

Ответ 1

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

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

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

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

Вкратце: вам не нужно удалять объекты Task в 99% случаев.

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

  • Начиная с .Net 4.5, единственный раз, когда Task выделяет внутренний дескриптор ожидания (единственный неуправляемый ресурс в объекте Task), когда вы явно используете IAsyncResult.AsyncWaitHandle для Task и
  • Сам объект Task не имеет финализатора; дескриптор сам завернут в объект с финализатором, поэтому, если он не назначен, нет финализатора для запуска.

Ответ 2

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

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

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

Ответ 3

Мне бы хотелось увидеть, как кто-то взвесил технику, показанную на этом посту: Типы безопасного и забытого асинхронного вызова делегатов в С#

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

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());
}