← Назад к вопросам

Объясните разницу между Task.Run и Task.Factory.StartNew. Когда использовать каждый?

1.7 Middle🔥 201 комментариев
#ASP.NET и Web API#Тестирование

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Разница между Task.Run и Task.Factory.StartNew

Основное отличие

Ключевая разница между Task.Run и Task.Factory.StartNew заключается в их уровне абстракции и рекомендуемом использовании. Task.Run появился в .NET Framework 4.5 как упрощенная, более безопасная альтернатива Task.Factory.StartNew.

// Task.Run - современный подход
Task task1 = Task.Run(() => DoWork());

// Task.Factory.StartNew - более гибкий, но сложный
Task task2 = Task.Factory.StartNew(() => DoWork());

Детальное сравнение

Task.Run

  • По умолчанию использует TaskScheduler.Default (пул потоков)
  • Автоматически распаковывает вложенные задачи (no task unwrapping issues)
  • Рекомендуется для большинства сценариев запуска фоновых задач
  • Более безопасен по умолчанию
// Task.Run автоматически распаковывает Task<Task>
Task<int> result = Task.Run(async () => 
{
    await Task.Delay(100);
    return 42;
});

Task.Factory.StartNew

  • Требует явного указания параметров
  • Имеет больше перегрузок и опций настройки
  • Может создавать проблемы с распаковкой вложенных задач
  • Позволяет использовать кастомные планировщики
// Task.Factory.StartNew с явными параметрами
Task task = Task.Factory.StartNew(
    () => DoWork(),
    CancellationToken.None,
    TaskCreationOptions.DenyChildAttach,
    TaskScheduler.Default
);

Ключевые параметры Task.Factory.StartNew

Task.Factory.StartNew предоставляет дополнительные возможности через параметры:

  1. TaskCreationOptions - управление поведением задачи
  2. TaskScheduler - выбор планировщика задач
  3. Продвинутые сценарии - привязка к контексту синхронизации
// Пример с TaskCreationOptions
Task longRunningTask = Task.Factory.StartNew(
    () => ProcessData(),
    TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness
);

Когда использовать каждый подход

Используйте Task.Run когда:

  • Запускаете CPU-bound операции в фоне
  • Нужен простой асинхронный вызов метода
  • Работаете с async/await паттерном
  • В большинстве повседневных сценариев
// Идеальный вариант для Task.Run
public async Task<int> CalculateAsync()
{
    return await Task.Run(() => 
    {
        // CPU-intensive work
        return PerformComplexCalculation();
    });
}

Используйте Task.Factory.StartNew когда:

  • Требуется контроль над TaskScheduler
  • Нужны специфические TaskCreationOptions
  • Создаете долго выполняющиеся задачи (LongRunning)
  • Работаете с кастомными планировщиками задач
  • Требуется привязка к контексту UI потока (через TaskScheduler.FromCurrentSynchronizationContext)
// Специфический сценарий для Task.Factory.StartNew
Task uiTask = Task.Factory.StartNew(
    () => UpdateUIComponent(),
    CancellationToken.None,
    TaskCreationOptions.None,
    TaskScheduler.FromCurrentSynchronizationContext() // Для UI обновлений
);

Проблема распаковки задач

Важное отличие - обработка вложенных задач:

// Task.Run - автоматическое распаковывание
Task<Task<int>> nested1 = Task.Run(async () => await GetValueAsync());
// Результат: Task<int>, а не Task<Task<int>>

// Task.Factory.StartNew - требуется Unwrap
Task<Task<int>> nested2 = Task.Factory.StartNew(async () => await GetValueAsync());
Task<int> unwrapped = nested2.Unwrap(); // Требуется явное распаковывание

Рекомендации по производительности

  • Для коротких операций используйте Task.Run с пулом потоков
  • Для долгих операций используйте Task.Factory.StartNew с TaskCreationOptions.LongRunning
  • Избегайте создания излишних задач для синхронных операций
// Неправильно - излишняя обертка
Task.Run(() => Console.WriteLine("Hello")); 

// Правильно - для CPU-bound операций
Task.Run(() => PerformExpensiveComputation());

Современные best practices

  1. Предпочитайте Task.Run для большинства сценариев
  2. Используйте async/await вместо прямого создания задач когда возможно
  3. Выбирайте Task.Factory.StartNew только при необходимости специфических опций
  4. Всегда обрабатывайте исключения в задачах
// Современный подход с async/await
public async Task ProcessDataAsync()
{
    try
    {
        var result = await Task.Run(() => ComputeResult());
        await ProcessResultAsync(result);
    }
    catch (Exception ex)
    {
        // Обработка исключений
        Logger.LogError(ex);
    }
}

Заключение

Task.Run - это рекомендуемый, безопасный по умолчанию выбор для запуска фоновых задач в современном C#. Task.Factory.StartNew остается полезным инструментом для продвинутых сценариев, где требуется тонкий контроль над созданием и планированием задач. Начиная с .NET 4.5, Microsoft рекомендует использовать Task.Run как основной метод запуска задач, резервируя Task.Factory.StartNew для специфических случаев, требующих дополнительной настройки.