Quelle est la différence entre Task.Run () et Task.Factory.StartNew ()


192

J'ai la méthode:

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

Et je veux démarrer cette méthode dans une nouvelle tâche. Je peux commencer une nouvelle tâche comme celle-ci

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

ou ca

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

Mais y a-t-il une différence entre Task.Run()et Task.Factory.StartNew(). Les deux utilisent ThreadPool et démarrent Method () immédiatement après la création de l'instance de la tâche. Quand devrions-nous utiliser la première variante et la seconde?


6
En fait, StartNew n'a pas besoin d'utiliser le ThreadPool, voir le blog auquel j'ai lié dans ma réponse. Le problème est StartNewpar défaut les utilisations TaskScheduler.Currentqui peuvent être le pool de threads mais également le thread d'interface utilisateur.
Scott Chamberlain

Réponses:


197

La deuxième méthode, Task.Runa été introduite dans une version ultérieure du framework .NET (dans .NET 4.5).

Cependant, la première méthode Task.Factory.StartNew,, vous donne la possibilité de définir beaucoup de choses utiles sur le fil que vous souhaitez créer, mais Task.Runne fournit pas cela.

Par exemple, disons que vous souhaitez créer un fil de tâche de longue durée. Si un thread du pool de threads va être utilisé pour cette tâche, cela peut être considéré comme un abus du pool de threads.

Une chose que vous pourriez faire pour éviter cela serait d'exécuter la tâche dans un thread séparé. Un thread nouvellement créé qui serait dédié à cette tâche et serait détruit une fois votre tâche terminée. Vous ne pouvez pas y parvenir avec le Task.Run, alors que vous pouvez le faire avec le Task.Factory.StartNew, comme ci-dessous:

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

Comme il est indiqué ici :

Ainsi, dans le .NET Framework 4.5 Developer Preview, nous avons introduit la nouvelle méthode Task.Run. Cela ne rend nullement obsolète Task.Factory.StartNew, mais devrait plutôt être simplement considéré comme un moyen rapide d'utiliser Task.Factory.StartNew sans avoir besoin de spécifier un tas de paramètres. C'est un raccourci. En fait, Task.Run est en fait implémenté en termes de la même logique que celle utilisée pour Task.Factory.StartNew, en passant simplement quelques paramètres par défaut. Lorsque vous passez une action à Task.Run:

Task.Run(someAction);

c'est exactement équivalent à:

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

4
J'ai un morceau de code où la déclaration that’s exactly equivalent tone tient pas.
Emaborsa du

7
@Emaborsa J'apprécierais si vous pouviez poster ce morceau de code et élaborer votre argument. Merci d'avance !
Christos

4
@Emaborsa Vous pouvez créer un résumé , gist.github.com , et le partager. Cependant, à l'exception du partage de cet élément essentiel, veuillez préciser comment vous êtes arrivé au résultat que la phrase tha's exactly equivalent tone tient pas. Merci d'avance. Ce serait bien d'expliquer en commentant votre code. Merci :)
Christos

8
Il convient également de mentionner que Task.Run déballe la tâche imbriquée par défaut. Je recommande de lire cet article sur les différences majeures: blogs.msdn.microsoft.com/pfxteam/2011/10/24/…
Pawel Maga

1
@ The0bserver non, ça l'est TaskScheduler.Default. Veuillez jeter un œil ici referencesource.microsoft.com/#mscorlib/system/threading/Tasks/… .
Christos

47

Les gens ont déjà mentionné que

Task.Run(A);

Est équivalent à

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

Mais personne n'a mentionné ça

Task.Factory.StartNew(A);

Est équivalent à:

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

Comme vous pouvez le voir, deux paramètres sont différents pour Task.Runet Task.Factory.StartNew:

  1. TaskCreationOptions- Task.Runutilise TaskCreationOptions.DenyChildAttachce qui signifie que les tâches enfants ne peuvent pas être attachées au parent, considérez ceci:

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

    Lorsque nous invoquons parentTask.Wait(), childTaskne sera pas attendu, même si nous l'avons spécifié TaskCreationOptions.AttachedToParent, c'est parce qu'il TaskCreationOptions.DenyChildAttachinterdit aux enfants de s'y attacher. Si vous exécutez le même code avec Task.Factory.StartNewau lieu de Task.Run, parentTask.Wait()attendra childTaskcar Task.Factory.StartNewutiliseTaskCreationOptions.None

  2. TaskScheduler- Task.Runutilise TaskScheduler.Defaultce qui signifie que le planificateur de tâches par défaut (celui qui exécute les tâches sur le pool de threads) sera toujours utilisé pour exécuter les tâches. Task.Factory.StartNewd'autre part utilise TaskScheduler.Currentce qui signifie planificateur du thread actuel, cela pourrait être TaskScheduler.Defaultmais pas toujours. En fait, lors du développement Winformsou des WPFapplications, il est nécessaire de mettre à jour l'interface utilisateur à partir du thread actuel, pour ce faire, les gens utilisent le TaskScheduler.FromCurrentSynchronizationContext()planificateur de tâches, si vous créez involontairement une autre tâche de longue durée dans la tâche qui utilisait le TaskScheduler.FromCurrentSynchronizationContext()planificateur, l'interface utilisateur sera gelée. Une explication plus détaillée de ceci peut être trouvée ici

Donc, généralement, si vous n'utilisez pas de tâche enfant imbriquée et que vous voulez toujours que vos tâches soient exécutées sur le pool de threads, il est préférable de l'utiliser Task.Run, sauf si vous avez des scénarios plus complexes.


1
C'est une astuce fantastique, devrait être la réponse acceptée
Ali Bayat

30

Consultez cet article de blog qui décrit la différence. En gros, faire:

Task.Run(A)

C'est la même chose que de faire:

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

28

Il a Task.Runété introduit dans la nouvelle version du framework .NET et il est recommandé .

À partir du .NET Framework 4.5, la méthode Task.Run est la méthode recommandée pour lancer une tâche liée au calcul. Utilisez la méthode StartNew uniquement lorsque vous avez besoin d'un contrôle précis pour une tâche de longue durée liée au calcul.

Le Task.Factory.StartNewa plus d'options, Task.Runc'est un raccourci:

La méthode Run fournit un ensemble de surcharges qui facilitent le démarrage d'une tâche à l'aide des valeurs par défaut. C'est une alternative légère aux surcharges StartNew.

Et par raccourci, j'entends un raccourci technique :

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

21

Selon cet article de Stephen Cleary, Task.Factory.StartNew () est dangereux:

Je vois beaucoup de code sur les blogs et dans les questions SO qui utilisent Task.Factory.StartNew pour faire tourner le travail sur un fil d'arrière-plan. Stephen Toub a un excellent article de blog qui explique pourquoi Task.Run est meilleur que Task.Factory.StartNew, mais je pense que beaucoup de gens ne l'ont tout simplement pas lu (ou ne le comprennent pas). Donc, j'ai repris les mêmes arguments, ajouté un langage plus énergique, et nous verrons comment cela se passe. :) StartNew offre bien plus d'options que Task.Run, mais c'est assez dangereux, comme nous le verrons. Vous devriez préférer Task.Run à Task.Factory.StartNew en code asynchrone.

Voici les raisons réelles:

  1. Ne comprend pas les délégués asynchrones. C'est en fait le même que le point 1 dans les raisons pour lesquelles vous voudriez utiliser StartNew. Le problème est que lorsque vous passez un délégué asynchrone à StartNew, il est naturel de supposer que la tâche renvoyée représente ce délégué. Toutefois, étant donné que StartNew ne comprend pas les délégués asynchrones, ce que cette tâche représente en réalité n'est que le début de ce délégué. C'est l'un des premiers pièges que rencontrent les codeurs lors de l'utilisation de StartNew dans du code asynchrone.
  2. Planificateur par défaut déroutant. OK, l'heure des questions astucieuses: dans le code ci-dessous, sur quel thread la méthode «A» fonctionne-t-elle?
Task.Factory.StartNew(A);

private static void A() { }

Eh bien, vous savez que c'est une question piège, hein? Si vous avez répondu «un thread de pool de threads», je suis désolé, mais ce n'est pas correct. «A» fonctionnera sur tout ce que TaskScheduler est en cours d'exécution!

Cela signifie donc qu'il pourrait potentiellement s'exécuter sur le thread de l'interface utilisateur si une opération se termine et qu'il revient au thread de l'interface utilisateur en raison d'une continuation, comme l'explique plus en détail Stephen Cleary dans son article.

Dans mon cas, j'essayais d'exécuter des tâches en arrière-plan lors du chargement d'une grille de données pour une vue tout en affichant une animation chargée. L'animation occupée ne s'affichait pas lors de l'utilisation, Task.Factory.StartNew()mais l'animation s'affichait correctement lorsque je suis passé à Task.Run().

Pour plus de détails, veuillez consulter https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html


1

Outre les similitudes, à savoir Task.Run () étant un raccourci pour Task.Factory.StartNew (), il y a une différence minime entre leur comportement en cas de délégués sync et async.

Supposons qu'il existe deux méthodes suivantes:

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

public int GetInt()
{
    return 1;
}

Considérons maintenant le code suivant.

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

Ici, sync1 et sync2 sont de type Task <int>

Cependant, la différence vient dans le cas des méthodes asynchrones.

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

Dans ce scénario, async1 est de type Task <int>, cependant async2 est de type Task <Task <int>>


Oui, car Task.Runa intégré la fonctionnalité de déballage de la Unwrapméthode. Voici un article de blog qui explique le raisonnement derrière cette décision.
Theodor Zoulias

-8

Dans mon application qui appelle deux services, j'ai comparé à la fois Task.Run et Task.Factory.StartNew . J'ai trouvé que dans mon cas, les deux fonctionnent bien. Cependant, le second est plus rapide.


Je ne sais pas pourquoi cette réponse mérite "10" votes à la baisse, même si elle n'est peut-être pas correcte ou utile ...
Mayer Spitzer
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.