Яка різниця між Task.Run () та Task.Factory.StartNew ()


191

У мене є метод:

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 і запускають метод () відразу після створення екземпляра Завдання. Коли нам слід використовувати перший варіант, а коли другий?


6
Власне, StartNew не повинен використовувати ThreadPool, дивіться блог, до якого я пов’язаний у своїй відповіді. Проблема полягає StartNewу використанні за замовчуванням, TaskScheduler.Currentяке може бути пулом потоків, але також може бути потоком UI.
Скотт Чемберлен

Відповіді:


197

Другий метод, Task.Runбув введений в більш пізній версії рамки .NET (в .NET 4.5).

Однак перший метод, Task.Factory.StartNew дає вам змогу визначити багато корисних речей про нитку, яку ви хочете створити, але Task.Runне надає цього.

Наприклад, скажімо, що ви хочете створити довгу нитку завдань. Якщо для цього завдання буде використовуватися нитка пулу потоків, то це може вважатися зловживанням пулом потоків.

Одне, що ви могли зробити для того, щоб уникнути цього, було б запустити завдання в окрему нитку. Новостворена нитка, яка була б присвячена цій задачі та була б знищена, як тільки ваше завдання буде виконане. Ви не можете досягти цього з Task.Run, в той час як ви можете зробити це з Task.Factory.StartNew, як показано нижче:

Task.Factory.StartNew(..., TaskCreationOptions.LongRunning);

Як зазначено тут :

Отже, у перегляді розробників .NET Framework 4.5 ми представили новий метод Task.Run. Це ні в якому разі не застаріє Task.Factory.StartNew, а, швидше, слід розглядати як швидкий спосіб використання Task.Factory.StartNew, не вказуючи купу параметрів. Це ярлик. Насправді, Task.Run реально реалізований з тієї ж логіки, що і для Task.Factory.StartNew, просто передаючи деякі параметри за замовчуванням. Коли ви передаєте дію Task.Run:

Task.Run(someAction);

це рівнозначно:

Task.Factory.StartNew(someAction, 
    CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

4
У мене є фрагмент коду, де державний документ that’s exactly equivalent toне втримується.
Емаборса

7
@Emaborsa Я б вдячний, якщо ви можете опублікувати цей фрагмент коду та розробити свій аргумент. Спасибі заздалегідь !
Крістос

4
@Emaborsa Ви можете створити gist.github.com і поділитися ним. Однак, окрім того, щоб поділитися цим суттю, вкажіть, будь ласка, вкажіть, як ви дійшли до того результату, який ця фраза tha's exactly equivalent toне відповідає. Заздалегідь спасибі. Було б непогано пояснити, коментуючи свій код. Дякую :)
Крістос

8
Також варто згадати, що за замовчуванням розгортати вкладене завдання Task.Run. Рекомендую прочитати цю статтю про основні відмінності: blogs.msdn.microsoft.com/pfxteam/2011/10/24/…
Pawel Maga

1
@ The0bserver nope, це так TaskScheduler.Default. Перегляньте тут посилання referenceource.microsoft.com/#mscorlib/system/threading/Tasks/… .
Христос

46

Про це вже згадували люди

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:

  1. TaskCreationOptions- Task.Runвикористовує, 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його не чекатимуть, незважаючи на те, що ми це вказали TaskCreationOptions.AttachedToParent, це тому, що TaskCreationOptions.DenyChildAttachзабороняє дітям приєднуватися до нього. Якщо Task.Factory.StartNewзамість того Task.Run, що ви запускаєте той самий код , parentTask.Wait()буде чекати, childTaskоскільки Task.Factory.StartNewвикористовуєTaskCreationOptions.None

  2. TaskScheduler- Task.Runвикористовує, TaskScheduler.Defaultщо означає, що планувальник завдань за замовчуванням (той, що виконує завдання в пулі потоків) завжди буде використовуватися для виконання завдань. Task.Factory.StartNewз іншого боку, використовує, TaskScheduler.Currentщо означає планувальник поточного потоку, це може бути, TaskScheduler.Defaultале не завжди. Насправді під час розробки Winformsабо WPFзастосувань потрібно оновити інтерфейс користувача з поточного потоку, щоб це використовувало TaskScheduler.FromCurrentSynchronizationContext()планувальник завдань, якщо ви ненавмисно створили ще одну тривалу задачу всередині завдання, яка використовувала TaskScheduler.FromCurrentSynchronizationContext()планувальник, користувальницький інтерфейс буде заморожено. Більш детальне пояснення цього можна знайти тут

Тож, як правило, якщо ви не використовуєте вкладені завдання для дітей і завжди хочете, щоб ваші завдання виконувались у Thread Pool, краще використовувати Task.Run, якщо у вас немає складніших сценаріїв.


1
Це фантастична порада, має бути прийнята відповідь
Алі Баят


28

Task.RunБув введений в новій версії платформи .NET , і це рекомендується .

Починаючи з .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);
}

21

Відповідно до цієї публікації Стівена Клірі, Task.Factory.StartNew () небезпечний:

Я бачу багато коду в блогах і в питаннях SO, які використовують Task.Factory.StartNew, щоб активізувати роботу над фоновою ниткою. Стівен Туб має чудову статтю в блозі, яка пояснює, чому Task.Run кращий за Task.Factory.StartNew, але я думаю, що багато людей просто не читали його (або не розуміють цього). Отже, я взяв ті самі аргументи, додав трохи більш сильної мови, і ми побачимо, як це відбувається. :) StartNew пропонує набагато більше варіантів, ніж Task.Run, але це досить небезпечно, як ми побачимо. Вам слід віддати перевагу Task.Run над Task.Factory.StartNew у коді асинхронізації.

Ось фактичні причини:

  1. Не розуміє асинхронних делегатів. Це насправді те саме, що пункт 1 в причинах, чому ви хочете використовувати StartNew. Проблема полягає в тому, що коли ви передаєте делегата асинхронізу до StartNew, природно припустити, що повернута задача являє собою цей делегат. Однак, оскільки StartNew не розуміє асинхронних делегатів, те, що ця задача насправді являє, є лише початком цього делегата. Це одна з перших підводних каменів, з якими стикаються кодери під час використання StartNew в коді асинхронізації.
  2. Заплутаний планувальник за замовчуванням. Гаразд, підкажіть час запитання: у коді нижче, на якому потоці працює метод “A”?
Task.Factory.StartNew(A);

private static void A() { }

Ну, ти знаєш, що це хитрі питання, так? Якщо ви відповіли "нитка пулу ниток", вибачте, але це невірно. "A" буде працювати на будь-якому завданні програми виконання завдань!

Це означає, що потенційно він може працювати на потоці користувальницького інтерфейсу, якщо операція завершиться, і вона повернеться до потоку інтерфейсу через продовження, як Стівен Клірі пояснює більш повно у своїй посаді.

У моєму випадку я намагався запускати завдання у фоновому режимі під час завантаження сітки даних для перегляду, одночасно показуючи зайняту анімацію. Анімація зайнята не відображалася під час використання, Task.Factory.StartNew()але анімація відображалася належним чином, коли я перейшовTask.Run() .

Для детальної інформації див. Https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html


1

Крім схожості, тобто Task.Run () є скороченням для Task.Factory.StartNew (), між їх поведінкою у випадку делегатів синхронізації та асинхронізації є хвилинна різниця.

Припустимо, існують два способи:

public async Task<int> GetIntAsync()
{
    return Task.FromResult(1);
}

public int GetInt()
{
    return 1;
}

Тепер розглянемо наступний код.

var sync1 = Task.Run(() => GetInt());
var sync2 = Task.Factory.StartNew(() => GetInt());

Тут і sync1, і sync2 мають тип Task <int>

Однак різниця виникає у випадку асинхронних методів.

var async1 = Task.Run(() => GetIntAsync());
var async2 = Task.Factory.StartNew(() => GetIntAsync());

У цьому сценарії async1 має тип Task <int>, однак async2 має тип Task <Task <int>>


Так, оскільки Task.Runвбудована функція розгортання Unwrapметоду. Ось допис у блозі, де пояснюються мотиви цього рішення.
Теодор Зуліяс

-8

У своїй програмі, яка викликає дві служби, я порівняв і Task.Run, і Task.Factory.StartNew . Я виявив, що в моєму випадку вони обоє добре працюють. Однак другий швидше.


Я не знаю, чому ця відповідь заслуговує на "10" голосів вниз, хоча це може бути не правильним чи корисним ...
Mayer Spitzer
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.