Jaka jest różnica między Task.Run () a Task.Factory.StartNew ()

191

Mam metodę:

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

I chcę rozpocząć tę metodę w nowym zadaniu. Mogę rozpocząć nowe zadanie w ten sposób

var task = Task.Factory.StartNew(new Action(Method));

albo to

var task = Task.Run(new Action(Method));

Ale czy jest jakaś różnica między Task.Run()i Task.Factory.StartNew(). Obaj używają ThreadPool i uruchamiają Method () natychmiast po utworzeniu instancji zadania. Kiedy powinniśmy użyć pierwszego wariantu, a kiedy drugiego?

Sergiy Lichenko
źródło
6
W rzeczywistości StartNew nie musi korzystać z ThreadPool, zobacz blog, do którego dałem link w mojej odpowiedzi. Problemem są StartNewdomyślnie zastosowania, TaskScheduler.Currentktóre mogą być pulą wątków, ale mogą być także wątkiem interfejsu użytkownika.
Scott Chamberlain,

Odpowiedzi:

197

Druga metoda Task.Runzostała wprowadzona w późniejszej wersji środowiska .NET (w .NET 4.5).

Jednak pierwsza metoda Task.Factory.StartNewdaje możliwość zdefiniowania wielu przydatnych rzeczy na temat wątku, który chcesz utworzyć, a Task.Runnie zapewnia tego.

Załóżmy na przykład, że chcesz utworzyć długo działający wątek zadania. Jeśli do tego zadania zostanie użyty wątek z puli wątków, można to uznać za nadużycie puli wątków.

Aby tego uniknąć, wystarczy uruchomić zadanie w osobnym wątku. Nowo utworzony wątek, który byłby poświęcony temu zadaniu i zostałby zniszczony po zakończeniu zadania. Nie możesz tego Task.Runzrobić za pomocą , podczas gdy możesz to zrobić za pomocą Task.Factory.StartNew, jak poniżej:

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

Jak stwierdzono tutaj :

Tak więc w .NET Framework 4.5 Developer Preview wprowadziliśmy nową metodę Task.Run. W żaden sposób nie jest to przestarzałe Task.Factory.StartNew, ale raczej powinno być traktowane jako szybki sposób korzystania z Task.Factory.StartNew bez konieczności podawania szeregu parametrów. To skrót. W rzeczywistości Task.Run jest faktycznie zaimplementowany w kategoriach tej samej logiki, co w Task.Factory.StartNew, po prostu przekazując niektóre parametry domyślne. Po przekazaniu akcji do zadania Uruchom:

Task.Run(someAction);

to dokładnie odpowiada:

Task.Factory.StartNew(someAction, 
    CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
Christos
źródło
4
Mam fragment kodu, w którym that’s exactly equivalent tonie ma instrukcja Statemente .
Emaborsa,
7
@Emaborsa byłbym wdzięczny, gdybyś mógł opublikować ten fragment kodu i rozwinąć swój argument. Z góry dziękuję !
Christos,
4
@Emaborsa Możesz stworzyć gist, gist.github.com i udostępnić go. Jednak oprócz dzielenia się tą treścią, proszę określić, w jaki sposób doszedłeś do wyniku, którego wyrażenie tha's exactly equivalent tonie zawiera. Z góry dziękuję. Byłoby miło wyjaśnić z komentarzem do twojego kodu. Dzięki :)
Christos
8
Warto również wspomnieć, że Task.Run domyślnie rozpakuj zagnieżdżone zadanie. Polecam przeczytać ten artykuł o głównych różnicach: blogs.msdn.microsoft.com/pfxteam/2011/10/24/…
Pawel Maga
1
@ The0bserver nie, to prawda TaskScheduler.Default. Proszę zajrzeć tutaj odniesieniaource.microsoft.com/#mscorlib/system/threading/Tasks/… .
Christos
46

Ludzie już o tym wspominali

Task.Run(A);

Jest równa

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

Ale nikt o tym nie wspominał

Task.Factory.StartNew(A);

Jest równa:

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Current);

Jak widać, dwa parametry są różne dla Task.Runi Task.Factory.StartNew:

  1. TaskCreationOptions- Task.Runzastosowania, TaskCreationOptions.DenyChildAttachco oznacza, że ​​zadania dla dzieci nie mogą być przypisane do rodzica, rozważ to:

    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.");

    Kiedy będziemy wzywać parentTask.Wait(), childTasknie będziemy oczekiwać, nawet jeśli postanowiliśmy TaskCreationOptions.AttachedToParent, TaskCreationOptions.DenyChildAttachdzieje się tak dlatego, że zabrania dzieciom przyłączania się do niego. Jeśli uruchomisz ten sam kod Task.Factory.StartNewzamiast Task.Run, parentTask.Wait()zaczeka na, childTaskponieważ Task.Factory.StartNewużywaTaskCreationOptions.None

  2. TaskScheduler - Task.Run Używa, TaskScheduler.Defaultco oznacza, że ​​do uruchamiania zadań zawsze będzie używany domyślny harmonogram zadań (ten, który uruchamia zadania w puli wątków). Task.Factory.StartNewz drugiej strony używa, TaskScheduler.Currentco oznacza harmonogram bieżącego wątku, może być, TaskScheduler.Defaultale nie zawsze. W rzeczywistości podczas programowania Winformslub WPFaplikacji wymagana jest aktualizacja interfejsu użytkownika z bieżącego wątku, aby to zrobić, ludzie używają TaskScheduler.FromCurrentSynchronizationContext()harmonogramu zadań, jeśli przypadkowo utworzysz inne długo działające zadanie w ramach zadania, które korzystało z TaskScheduler.FromCurrentSynchronizationContext()harmonogramu, interfejs użytkownika zostanie zawieszony. Bardziej szczegółowe wyjaśnienie tego można znaleźć tutaj

Ogólnie więc, jeśli nie używasz zadania zagnieżdżonego i zawsze chcesz, aby twoje zadania były wykonywane w puli wątków, lepiej jest użyć Task.Run, chyba że masz bardziej złożone scenariusze.

Mychajło Seniutowicz
źródło
1
To fantastyczna wskazówka, powinna być zaakceptowana odpowiedź
Ali Bayat
30

Zobacz ten artykuł na blogu, który opisuje różnicę. Zasadniczo robienie:

Task.Run(A)

To jest to samo co robienie:

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);   
Scott Chamberlain
źródło
28

Został Task.Runwprowadzony w nowszej wersji .NET Framework i jest zalecany .

Począwszy od programu .NET Framework 4.5, metoda Task.Run jest zalecanym sposobem uruchamiania zadania związanego z obliczeniami. Z metody StartNew należy korzystać tylko wtedy, gdy wymagana jest precyzyjna kontrola dla długotrwałego zadania związanego z obliczeniami.

Task.Factory.StartNewMa więcej opcji, Task.Runjest skrótem:

Metoda Run zapewnia zestaw przeciążeń, które ułatwiają rozpoczęcie zadania przy użyciu wartości domyślnych. Jest to lekka alternatywa dla przeciążeń StartNew.

I przez skrót mam na myśli techniczny skrót :

public static Task Run(Action action)
{
    return Task.InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default,
        TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark);
}
Zein Makki
źródło
21

Zgodnie z tym postem Stephena Cleary'ego Task.Factory.StartNew () jest niebezpieczny:

Widzę dużo kodu na blogach i w SO pytaniach, które używają Task.Factory.StartNew, aby rozwinąć pracę nad wątkiem w tle. Stephen Toub ma doskonały artykuł na blogu, który wyjaśnia, dlaczego Task.Run jest lepszy niż Task.Factory.StartNew, ale myślę, że wiele osób po prostu go nie przeczytało (lub go nie rozumie). Wziąłem więc te same argumenty, dodałem mocniejszy język i zobaczymy, jak to będzie. :) StartNew oferuje znacznie więcej opcji niż Task.Run, ale jest to dość niebezpieczne, jak zobaczymy. Wolisz Task.Run niż Task.Factory.StartNew w kodzie asynchronicznym.

Oto rzeczywiste powody:

  1. Nie rozumie delegatów asynchronicznych. Jest to w rzeczywistości to samo co w punkcie 1 z powodów, dla których chcesz użyć StartNew. Problem polega na tym, że gdy przekazujesz delegata asynchronicznego do StartNew, naturalne jest założenie, że zwrócone zadanie reprezentuje tego delegata. Ponieważ jednak StartNew nie rozumie asynchronicznych delegatów, to zadanie w rzeczywistości reprezentuje tylko początek tego delegata. Jest to jedna z pierwszych pułapek napotykanych przez programistów podczas używania StartNew w kodzie asynchronicznym.
  2. Mylący domyślny harmonogram. OK, podstępny czas na pytanie: w poniższym kodzie na jakim wątku działa metoda „A”?
Task.Factory.StartNew(A);

private static void A() { }

Wiesz, że to podchwytliwe pytanie, co? Jeśli odpowiedziałeś „wątek z puli wątków”, przepraszam, ale to nieprawda. „A” będzie działać na każdym aktualnie wykonywanym TaskScheduler!

Oznacza to, że może potencjalnie działać w wątku interfejsu użytkownika, jeśli operacja zostanie zakończona, a następnie powróci do wątku interfejsu użytkownika z powodu kontynuacji, jak Stephen Cleary wyjaśnia pełniej w swoim poście.

W moim przypadku próbowałem uruchomić zadania w tle podczas ładowania siatki danych dla widoku, jednocześnie wyświetlając zajętą ​​animację. Animacja zajętości nie wyświetlała się podczas używania, Task.Factory.StartNew()ale animacja wyświetlała się poprawnie po przełączeniu naTask.Run() .

Aby uzyskać szczegółowe informacje, zobacz https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html

użytkownik8128167
źródło
1

Oprócz podobieństw, tj. Task.Run () jest skrótem od Task.Factory.StartNew (), istnieje niewielka różnica między ich zachowaniem w przypadku delegowania synchronizacji i asynchronizacji.

Załóżmy, że istnieją dwie metody:

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

public int GetInt()
{
    return 1;
}

Teraz rozważ następujący kod.

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

Tutaj zarówno sync1, jak i sync2 są typu Zadanie <int>

Różnica pojawia się jednak w przypadku metod asynchronicznych.

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

W tym scenariuszu async1 jest typu Zadanie <int>, jednak async2 jest typu Zadanie <Zadanie <int>>

Shubham Sharma
źródło
Tak, ponieważ Task.Runma wbudowaną funkcję rozpakowywania Unwrapmetody. Oto post na blogu, który wyjaśnia uzasadnienie tej decyzji.
Theodor Zoulias
-8

W mojej aplikacji, która wywołuje dwie usługi, porównałem zarówno Task.Run, jak i Task.Factory.StartNew . Odkryłem, że w moim przypadku oba działają dobrze. Jednak drugi jest szybszy.

Devendra Rusia
źródło
Nie wiem, dlaczego ta odpowiedź zasługuje na głos „10”, choć może nie być poprawna lub pomocna ...
Mayer Spitzer