web-dev-qa-db-fra.com

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

J'ai 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 commencer 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 faut-il utiliser la première variante et quand la seconde?

141
Sergiy Lichenko

La deuxième méthode, Task.Run, a é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 un grand nombre de choses utiles sur le fil que vous souhaitez créer, alors que Task.Run ne le fournit pas.

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

Pour éviter cela, vous pouvez exécuter la tâche dans un thread distinct. 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 atteindre ceci 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 dit ici :

Ainsi, dans l’aperçu du développeur .NET Framework 4.5, nous avons introduit la nouvelle méthode Task.Run. Ceci n’observe aucunement Task.Factory.StartNew, mais doit simplement être considéré comme un moyen rapide de: utilisez Task.Factory.StartNew sans avoir à spécifier un ensemble de paramètres. C’est un raccourci. En fait, Task.Run est réellement implémenté selon la même logique que celle utilisée pour Task.Factory.StartNew, en passant juste 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);
152
Christos

Voir cet article de blog qui décrit la différence. Fondamentalement faire:

Task.Run(A)

Est-ce la même chose que faire:

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

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

À partir de .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 affiné pour une tâche de longue durée, liée au calcul.

Le Task.Factory.StartNew a plus d'options, le Task.Run 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 je veux dire technique raccourci :

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

Selon ce billet de Stephen Cleary, Task.Factory.StartNew () est dangereux:

Je vois beaucoup de code sur les blogs et dans SO questions qui utilisent Task.Factory.StartNew pour accélérer le travail sur un thread en 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 le lisent pas (ou ne le comprennent pas). J'ai donc repris les mêmes arguments, ajouté un langage plus percutant et nous verrons comment cela se passera. :) 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 async.

Voici les raisons réelles:

  1. Ne comprend pas les délégués asynchrones. Ceci est en fait identique au point 1 des raisons pour lesquelles vous souhaitez 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 réellement n’est que le début de ce délégué. C'est l'un des premiers pièges rencontrés par les codeurs lorsqu'ils utilisent StartNew en code asynchrone.
  2. Planificateur par défaut déroutant. OK, le temps de la question astuce: dans le code ci-dessous, sur quel thread la méthode “A” est-elle exécutée?
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 train d'exécuter!

Cela signifie donc qu'il pourrait éventuellement être exécuté sur le thread d'interface utilisateur si une opération est terminée et qu'il est renvoyé au thread d'interface utilisateur en raison d'une continuation, comme l'explique Stephen Cleary plus en détail dans son message.

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 occupée. L’animation occupée ne s’affiche pas lorsqu’on utilise Task.Factory.StartNew(), mais l’animation s’affiche correctement lorsque j’ai basculé sur Task.Run().

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

19
user8128167

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é que

Task.Factory.StartNew(A);

Est équivalent à:

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

Comme vous pouvez le constater, deux paramètres sont différents pour Task.Run et Task.Factory.StartNew:

  1. TaskCreationOptions - Task.Run utilise TaskCreationOptions.DenyChildAttach, ce 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(), childTask ne sera pas attendu, même si nous avons spécifié TaskCreationOptions.AttachedToParent, car c'est parce que TaskCreationOptions.DenyChildAttach interdit aux enfants de s'y attacher. Si vous exécutez le même code avec Task.Factory.StartNew au lieu de Task.Run, parentTask.Wait() attendra childTask car Task.Factory.StartNew utilisera TaskCreationOptions.None

  2. TaskScheduler - Task.Run utilise TaskScheduler.Default, ce 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 des tâches. Task.Factory.StartNew utilise d'autre part TaskScheduler.Current qui signifie planificateur du fil actuel, il peut s'agir de TaskScheduler.Default mais pas toujours. En fait, lors du développement d'applications Winforms ou WPF, il est nécessaire de mettre à jour l'interface utilisateur à partir du thread actuel. Pour ce faire, les utilisateurs utilisent le planificateur de tâches TaskScheduler.FromCurrentSynchronizationContext(), si vous créez involontairement une autre tâche longue à l'intérieur de la tâche. celui utilisé TaskScheduler.FromCurrentSynchronizationContext() programmateur l'interface utilisateur sera gelée. Une explication plus détaillée de ceci peut être trouvée ici

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

10