В чем разница между Task.Run() и Task.Factory.StartNew()
У меня есть метод:
private static void Method()
{
Console.WriteLine("Method() started");
for (var i = 0; i < 20; i++)
{
Console.WriteLine("Method() Counter = " + i);
Thread.Sleep(500);
}
Console.WriteLine("Method() finished");
}
И я хочу запустить этот метод в новой Задаче.
Я могу начать новую задачу следующим образом
var task = Task.Factory.StartNew(new Action(Method));
или
var task = Task.Run(new Action(Method));
Но есть ли разница между Task.Run()
и Task.Factory.StartNew()
. Оба они используют ThreadPool и запускают метод() сразу после создания экземпляра задачи. Когда мы должны использовать первый вариант и второй?
Ответы
Ответ 1
Второй метод Task.Run
был представлен в более поздней версии.NET Framework (в.NET 4.5).
Однако первый метод Task.Factory.StartNew
дает вам возможность определить много полезных вещей о потоке, который вы хотите создать, в то время как Task.Run
не предоставляет этого.
Например, скажем, что вы хотите создать длинный поток задач. Если для этой задачи будет использоваться поток пула потоков, то это можно считать злоупотреблением пулом потоков.
Одна вещь, которую вы могли бы сделать, чтобы избежать этого, - это запустить задачу в отдельном потоке. Недавно созданный поток, который будет посвящен этой задаче и будет уничтожен, как только ваша задача будет завершена. Вы не можете достичь этого с помощью Task.Run
, в то время как вы можете сделать это с помощью Task.Factory.StartNew
, как Task.Factory.StartNew
ниже:
Task.Factory.StartNew(..., TaskCreationOptions.LongRunning);
Как сказано здесь:
Итак, в.NET Framework 4.5 Developer Preview, weve представила новый метод Task.Run. Это никоим образом не устаревает Task.Factory.StartNew, а скорее просто следует рассматривать как быстрый способ использования Task.Factory.StartNew без необходимости указывать набор параметров. Его ярлык. Фактически Task.Run фактически реализуется с точки зрения той же логики, что и для Task.Factory.StartNew, просто передавая некоторые параметры по умолчанию. Когда вы передаете действие в задачу. Run:
Task.Run(someAction);
что в точности эквивалентно:
Task.Factory.StartNew(someAction,
CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
Ответ 2
См. Эту статью в блоге, в которой описывается разница. В основном делаю:
Task.Run(A)
То же самое, что и делать:
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
Ответ 3
Task.Run
был представлен в более новой версии .NET Framework, и это рекомендуется.
Начиная с .NET Framework 4.5, метод Task.Run является рекомендуемым способом запуска задачи, связанной с вычислениями. Используйте метод StartNew только тогда, когда вам требуется детальное управление для длительной задачи, связанной с вычислениями.
Task.Factory.StartNew
имеет больше опций, Task.Run
- это сокращение:
Метод Run предоставляет набор перегрузок, облегчающих запуск задачи с использованием значений по умолчанию. Это легкая альтернатива перегрузкам StartNew.
Под сокращением я подразумеваю технический ярлык:
public static Task Run(Action action)
{
return Task.InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default,
TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark);
}
Ответ 4
Согласно этому сообщению Стивена Клири, Task.Factory.StartNew() опасен:
Я вижу много кода в блогах и в SO вопросах, которые используют Task.Factory.StartNew для ускорения работы в фоновом потоке. У Стивена Тауба есть отличная статья в блоге, которая объясняет, почему Task.Run лучше, чем Task.Factory.StartNew, но я думаю, что многие просто не читают его (или не понимают). Итак, я взял те же аргументы, добавил несколько более убедительных формулировок и хорошо посмотрим, как это происходит. :) StartNew предлагает гораздо больше опций, чем Task.Run, но это довольно опасно, как видите. Вы должны предпочесть Task.Run, а не Task.Factory.StartNew в асинхронном коде.
Вот реальные причины:
- Не понимает асинхронных делегатов. На самом деле это то же самое, что и пункт 1 по причинам, по которым вы хотели бы использовать StartNew. Проблема заключается в том, что при передаче асинхронного делегата в StartNew естественно предположить, что возвращаемая задача представляет этот делегат. Однако, поскольку StartNew не понимает асинхронные делегаты, то, что фактически представляет эта задача, является только началом этого делегата. Это одна из первых ловушек, с которой сталкиваются кодеры при использовании StartNew в асинхронном коде.
- Запутанный планировщик по умолчанию. Хорошо, время для подвоха: в каком коде выполняется метод "А"?
Task.Factory.StartNew(A);
private static void A() { }
Ну, вы знаете, это вопрос с подвохом, а? Если вы ответили "поток пула потоков", извините, но это не правильно. "A" будет запускаться на любом выполняемом TaskScheduler!
Таким образом, это означает, что он может потенциально выполняться в потоке пользовательского интерфейса, если операция завершена, и он маршалирует обратно в поток пользовательского интерфейса из-за продолжения, как более подробно объясняет Стивен Клири в своем посте.
В моем случае я пытался запускать задачи в фоновом режиме при загрузке сетки данных для представления, а также при отображении занятой анимации. Task.Factory.StartNew()
анимация не отображается при использовании Task.Factory.StartNew()
но анимация отображается правильно, когда я переключился на Task.Run()
.
Для получения дополнительной информации, пожалуйста, смотрите https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html
Ответ 5
Люди уже упоминали, что
Task.Run(A);
Эквивалентно
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
Но никто не упомянул, что
Task.Factory.StartNew(A);
Эквивалентно:
Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Current);
Как вы можете видеть, два параметра для Task.Run
и Task.Factory.StartNew
:
-
TaskCreationOptions
- Task.Run
использует TaskCreationOptions.DenyChildAttach
что означает, что дочерние задачи не могут быть присоединены к родителю, TaskCreationOptions.DenyChildAttach
это:
var parentTask = Task.Run(() =>
{
var childTask = new Task(() =>
{
Thread.Sleep(10000);
Console.WriteLine("Child task finished.");
}, TaskCreationOptions.AttachedToParent);
childTask.Start();
Console.WriteLine("Parent task finished.");
});
parentTask.Wait();
Console.WriteLine("Main thread finished.");
Когда мы вызываем parentTask.Wait()
, childTask
не будет childTask
, даже несмотря на то, что мы указали для него TaskCreationOptions.AttachedToParent
, это потому, что TaskCreationOptions.DenyChildAttach
запрещает детям присоединяться к нему. Если вы запустите тот же код с Task.Factory.StartNew
вместо Task.Run
, parentTask.Wait()
будет ожидать childTask
потому что Task.Factory.StartNew
использует TaskCreationOptions.None
-
TaskScheduler
- Task.Run
использует TaskScheduler.Default
что означает, что для запуска задач всегда будет использоваться планировщик задач по умолчанию (тот, который запускает задачи в пуле потоков). Task.Factory.StartNew
с другой стороны, использует TaskScheduler.Current
что означает планировщик текущего потока, это может быть TaskScheduler.Default
но не всегда. Фактически, при разработке приложений Winforms
или WPF
требуется обновить пользовательский интерфейс из текущего потока, для этого люди используют TaskScheduler.FromCurrentSynchronizationContext()
задач TaskScheduler.FromCurrentSynchronizationContext()
, если вы непреднамеренно создаете другую долгосрочную задачу внутри задачи, в которой используется TaskScheduler.FromCurrentSynchronizationContext()
пользовательский интерфейс будет заморожен. Более подробное объяснение этого можно найти здесь
Поэтому, как правило, если вы не используете задачу с вложенными детьми и всегда хотите, чтобы ваши задачи выполнялись в Пуле потоков, лучше использовать Task.Run
, если у вас нет более сложных сценариев.
Ответ 6
Task.Factory.StartNew
разницу между Task.Factory.StartNew
и Task.Run
, я Task.Factory.StartNew
Task.Run
:
-
В .NET Framework (включая 4.7) Task.Run
- это метод расширения, который на самом деле является оберткой над Task.Factory.StartNew(action, cancellationToken, TaskCreationOptions.None, TaskScheduler.Default)
. См. Справочный исходный код Microsoft.
public static Task Run(Action action)
{
return Run(action, CancellationToken.None);
}
public static Task Run(Action action, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(action, cancellationToken, TaskCreationOptions.None, TaskScheduler.Default);
}
-
В .NET Core Task.Run
вызывает внутренний метод Task.InternalStartNew
, который планирует и запускает новую задачу. Так что это больше не оболочка Task.StartNew
метода Task.StartNew
. См. Исходный код .NET Core:
public static Task Run(Action action)
{
return Task.InternalStartNew(null, action, null, default, TaskScheduler.Default,
TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None);
}
-
В то же время StartNew
вызывает тот же метод Task.InternalStartNew
. См. Исходный код .NET Core:
public Task StartNew(Action action)
{
Task currTask = Task.InternalCurrent;
return Task.InternalStartNew(currTask, action, null, m_defaultCancellationToken, GetDefaultScheduler(currTask),
m_defaultCreationOptions, InternalTaskOptions.None);
}
-
Таким образом, оба метода вызывают один и тот же внутренний метод (InternalStartNew
) с разными параметрами:
internal static Task InternalStartNew(
Task creatingTask, Delegate action, object state, CancellationToken cancellationToken, TaskScheduler scheduler,
TaskCreationOptions options, InternalTaskOptions internalOptions)
{
// Validate arguments.
if (scheduler == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
}
// Create and schedule the task. This throws an InvalidOperationException if already shut down.
// Here we add the InternalTaskOptions.QueuedByRuntime to the internalOptions, so that TaskConstructorCore can skip the cancellation token registration
Task t = new Task(action, state, creatingTask, cancellationToken, options, internalOptions | InternalTaskOptions.QueuedByRuntime, scheduler);
t.ScheduleAndStart(false);
return t;
}
Ответ 7
В моем приложении, которое вызывает две службы, я сравнивал как Task.Run, так и Task.Factory.StartNew. Я обнаружил, что в моем случае оба они работают нормально. Однако второй быстрее.