Was ist der Unterschied zwischen Task.Run () und Task.Factory.StartNew ()

189

Ich habe Methode:

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

Und ich möchte diese Methode in einer neuen Aufgabe starten. Ich kann so eine neue Aufgabe starten

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

oder dieses

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

Aber gibt es einen Unterschied zwischen Task.Run()und Task.Factory.StartNew(). Beide verwenden ThreadPool und starten Method () unmittelbar nach dem Erstellen der Instanz der Task. Wann sollten wir die erste Variante verwenden und wann die zweite?

Sergiy Lichenko
quelle
6
Tatsächlich muss StartNew den ThreadPool nicht verwenden. Weitere Informationen finden Sie in dem Blog, auf das ich in meiner Antwort verlinkt habe. Das Problem besteht StartNewstandardmäßig in Verwendungen, bei TaskScheduler.Currentdenen es sich möglicherweise um den Thread-Pool, aber auch um den UI-Thread handelt.
Scott Chamberlain

Antworten:

194

Die zweite Methode wurde Task.Runin einer späteren Version des .NET Frameworks (in .NET 4.5) eingeführt.

Die erste Methode Task.Factory.StartNewbietet Ihnen jedoch die Möglichkeit, viele nützliche Dinge über den Thread zu definieren, den Sie erstellen möchten, Task.Runohne dies zu tun.

Angenommen, Sie möchten einen lang laufenden Task-Thread erstellen. Wenn ein Thread des Thread-Pools für diese Aufgabe verwendet wird, kann dies als Missbrauch des Thread-Pools angesehen werden.

Eine Möglichkeit, dies zu vermeiden, besteht darin, die Aufgabe in einem separaten Thread auszuführen. Ein neu erstellter Thread , der dieser Aufgabe gewidmet ist und nach Abschluss Ihrer Aufgabe zerstört wird. Sie können dies nicht mit dem erreichen Task.Run, während Sie dies mit dem folgenden tun können Task.Factory.StartNew:

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

Wie hier angegeben :

Daher haben wir in der .NET Framework 4.5-Entwicklervorschau die neue Task.Run-Methode eingeführt. Dies macht Task.Factory.StartNew in keiner Weise überflüssig, sondern sollte einfach als schnelle Möglichkeit zur Verwendung von Task.Factory.StartNew angesehen werden, ohne dass eine Reihe von Parametern angegeben werden müssen. Es ist eine Abkürzung. Tatsächlich wird Task.Run in Bezug auf dieselbe Logik implementiert, die für Task.Factory.StartNew verwendet wird, wobei nur einige Standardparameter übergeben werden. Wenn Sie eine Aktion an Task.Run übergeben:

Task.Run(someAction);

das ist genau gleichbedeutend mit:

Task.Factory.StartNew(someAction, 
    CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
Christos
quelle
4
Ich habe einen Code, den die Anweisung that’s exactly equivalent tonicht enthält.
Emaborsa
7
@ Emaborsa Ich würde mich freuen, wenn Sie diesen Code posten und Ihre Argumentation ausarbeiten könnten. Danke im Voraus !
Christos
4
@Emaborsa Sie können ein Gist erstellen, gist.github.com , und es teilen. Geben Sie jedoch an, wie Sie zu dem Ergebnis gekommen sind, das der Satz tha's exactly equivalent tonicht enthält , außer wenn Sie diesen Kern teilen . Danke im Voraus. Es wäre schön, mit einem Kommentar zu Ihrem Code zu erklären. Danke :)
Christos
8
Erwähnenswert ist auch, dass Task.Run die verschachtelte Task standardmäßig auspackt. Ich empfehle, diesen Artikel über wichtige Unterschiede zu lesen: blogs.msdn.microsoft.com/pfxteam/2011/10/24/…
Pawel Maga
1
@ The0bserver nein, das ist es TaskScheduler.Default. Bitte schauen Sie hier referencesource.microsoft.com/#mscorlib/system/threading/Tasks/… .
Christos
44

Das haben die Leute schon erwähnt

Task.Run(A);

Ist äquivalent zu

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

Aber das hat niemand erwähnt

Task.Factory.StartNew(A);

Ist äquivalent zu:

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

Wie Sie sehen können, unterscheiden sich zwei Parameter für Task.Runund Task.Factory.StartNew:

  1. TaskCreationOptions- Task.RunVerwendet, TaskCreationOptions.DenyChildAttachwas bedeutet, dass untergeordnete Aufgaben nicht an die Eltern angehängt werden können. Beachten Sie Folgendes:

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

    Wenn wir anrufen parentTask.Wait(), childTaskwird nicht erwartet, obwohl wir es angegeben TaskCreationOptions.AttachedToParenthaben, liegt dies daran, TaskCreationOptions.DenyChildAttachdass Kinder verbieten, sich daran zu binden. Wenn Sie den gleichen Code mit Task.Factory.StartNewanstelle von ausführen Task.Run, parentTask.Wait()wird auf childTaskweil Task.Factory.StartNewverwendet gewartetTaskCreationOptions.None

  2. TaskScheduler- Task.RunVerwendet, TaskScheduler.Defaultwas bedeutet, dass der Standard-Taskplaner (der Aufgaben im Thread-Pool ausführt) immer zum Ausführen von Aufgaben verwendet wird.Task.Factory.StartNewAuf der anderen Seite verwendet, TaskScheduler.Currentwas bedeutet, Scheduler des aktuellen Threads, es kann sein, TaskScheduler.Defaultaber nicht immer. Tatsächlich ist es bei der Entwicklung Winformsoder bei WPFAnwendungen erforderlich, die Benutzeroberfläche aus dem aktuellen Thread zu aktualisieren. Dazu TaskScheduler.FromCurrentSynchronizationContext()verwenden TaskScheduler.FromCurrentSynchronizationContext()die Benutzer den Taskplaner. Wenn Sie unbeabsichtigt eine andere Aufgabe mit langer Laufzeit innerhalb der Aufgabe erstellen, die den Planer verwendet hat, wird die Benutzeroberfläche eingefroren. Eine ausführlichere Erklärung hierzu finden Sie hier

Wenn Sie also keine verschachtelte untergeordnete Aufgabe verwenden und immer möchten, dass Ihre Aufgaben im Thread-Pool ausgeführt werden, ist es im Allgemeinen besser, sie zu verwenden Task.Run, es sei denn, Sie haben komplexere Szenarien.

Mykhailo Seniutovych
quelle
1
Dies ist ein fantastischer Tipp, sollte die akzeptierte Antwort sein
Ali Bayat
30

Siehe diesen Blog-Artikel , der den Unterschied beschreibt. Grundsätzlich tun:

Task.Run(A)

Ist das gleiche wie:

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);   
Scott Chamberlain
quelle
28

Das Task.Runwurde in einer neueren .NET Framework-Version eingeführt und wird empfohlen .

Ab .NET Framework 4.5 ist die Task.Run-Methode die empfohlene Methode zum Starten einer rechnergebundenen Aufgabe. Verwenden Sie die StartNew-Methode nur, wenn Sie eine fein abgestimmte Steuerung für eine lange laufende, rechnergebundene Aufgabe benötigen.

Das Task.Factory.StartNewhat mehr Möglichkeiten, das Task.Runist eine Abkürzung:

Die Run-Methode bietet eine Reihe von Überladungen, die das Starten einer Aufgabe mithilfe von Standardwerten vereinfachen. Es ist eine leichte Alternative zu den StartNew-Überladungen.

Und mit Kurzschrift meine ich eine technische Abkürzung :

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

Laut diesem Beitrag von Stephen Cleary ist Task.Factory.StartNew () gefährlich:

Ich sehe viel Code in Blogs und in SO-Fragen, die Task.Factory.StartNew verwenden, um die Arbeit an einem Hintergrund-Thread zu starten. Stephen Toub hat einen ausgezeichneten Blog-Artikel, der erklärt, warum Task.Run besser ist als Task.Factory.StartNew, aber ich denke, viele Leute haben es einfach nicht gelesen (oder verstehen es nicht). Also habe ich die gleichen Argumente genommen, eine stärkere Sprache hinzugefügt, und wir werden sehen, wie das geht. :) StartNew bietet viel mehr Optionen als Task.Run, aber es ist ziemlich gefährlich, wie wir sehen werden. Sie sollten Task.Run gegenüber Task.Factory.StartNew im asynchronen Code bevorzugen.

Hier sind die tatsächlichen Gründe:

  1. Versteht keine asynchronen Delegaten. Dies ist tatsächlich dasselbe wie Punkt 1 in den Gründen, warum Sie StartNew verwenden möchten. Das Problem ist, dass beim Übergeben eines asynchronen Delegaten an StartNew natürlich davon ausgegangen wird, dass die zurückgegebene Aufgabe diesen Delegaten darstellt. Da StartNew jedoch keine asynchronen Delegaten versteht, ist diese Aufgabe nur der Anfang dieses Delegaten. Dies ist eine der ersten Fallstricke, auf die Codierer stoßen, wenn sie StartNew in asynchronem Code verwenden.
  2. Verwirrender Standardplaner. OK, Trickfragezeit: Auf welchem ​​Thread läuft im folgenden Code die Methode "A"?
Task.Factory.StartNew(A);

private static void A() { }

Weißt du, es ist eine Trickfrage, oder? Wenn Sie auf "ein Thread-Pool-Thread" geantwortet haben, tut es mir leid, aber das ist nicht korrekt. "A" wird auf jedem TaskScheduler ausgeführt, der gerade ausgeführt wird!

Das bedeutet, dass es möglicherweise auf dem UI-Thread ausgeführt werden kann, wenn ein Vorgang abgeschlossen ist, und dass es aufgrund einer Fortsetzung zum UI-Thread zurückmarschiert, wie Stephen Cleary in seinem Beitrag ausführlicher erklärt.

In meinem Fall habe ich versucht, Aufgaben im Hintergrund auszuführen, wenn ein Datagrid für eine Ansicht geladen wurde, während gleichzeitig eine belebte Animation angezeigt wurde. Die belebte Animation wurde bei der Verwendung nicht angezeigt, Task.Factory.StartNew()aber die Animation wurde beim Umschalten ordnungsgemäß angezeigt Task.Run().

Weitere Informationen finden Sie unter https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html

user8128167
quelle
1

Abgesehen von den Ähnlichkeiten, dh Task.Run () ist eine Abkürzung für Task.Factory.StartNew (), gibt es einen winzigen Unterschied zwischen ihrem Verhalten bei synchronisierten und asynchronen Delegaten.

Angenommen, es gibt zwei Methoden:

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

public int GetInt()
{
    return 1;
}

Betrachten Sie nun den folgenden Code.

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

Hier sind sowohl sync1 als auch sync2 vom Typ Task <int>

Bei asynchronen Methoden besteht jedoch ein Unterschied.

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

In diesem Szenario ist async1 vom Typ Task <int>, async2 jedoch vom Typ Task <Task> <int>

Shubham Sharma
quelle
Ja, da Task.Rundie Entpackungsfunktionalität der UnwrapMethode integriert ist. Hier ist ein Blog-Beitrag, der die Gründe für diese Entscheidung erklärt.
Theodor Zoulias
-8

In meiner Anwendung, die zwei Dienste aufruft, habe ich sowohl Task.Run als auch Task.Factory.StartNew verglichen . Ich fand, dass in meinem Fall beide gut funktionieren. Der zweite ist jedoch schneller.

Devendra Rusia
quelle
Ich weiß nicht, warum diese Antwort "10" Abstimmungen verdient, obwohl sie möglicherweise nicht korrekt oder hilfreich ist ...
Mayer Spitzer