Sur les méthodes Task.Factory.StartNew et Task.Run

Lorsque vous créez des tâches à l'aide des méthodes Task.Factory.StartNew ou Task.Run, vous devez garder certains points importants à l'esprit lors de l'écriture de code asynchrone. Dans la plupart des cas, il est conseillé d'éviter d'utiliser la méthode Task.Factory.StartNew si vous travaillez avec du code asynchrone. Si vous travaillez avec du code parallèle, je dirais que StartNew est un bon choix.

Un planificateur de tâches est un composant responsable de la planification des tâches; Le framework .Net vous fournit deux planificateurs de tâches. Il existe le planificateur de tâches par défaut qui s'exécute sur le pool de threads .Net Framework, et il y a le planificateur de tâches qui s'exécute sur le contexte de synchronisation d'une cible spécifiée. Le planificateur de tâches par défaut suffira la plupart du temps, mais vous pouvez également créer votre propre planificateur de tâches personnalisé pour fournir des fonctionnalités supplémentaires. Pour créer votre propre planificateur de tâches personnalisé, vous devez créer une classe qui étend la classe System.Threading.Tasks.TaskScheduler.

Comment créer des tâches à l'aide de la bibliothèque parallèle de tâches?

Il existe plusieurs façons de créer et de démarrer des tâches dans .Net. Vous devez utiliser la classe System.Threading.Tasks.Task ou System.Threading.Tasks.Task pour créer des tâches (une unité de travail planifiable). Alors que le premier est utilisé pour créer une tâche qui ne renvoie pas de valeur, le dernier est utilisé pour créer des tâches qui ont des valeurs de retour. La propriété Task.Factory est une instance de la classe TaskFactory. Cette propriété est utilisée pour créer et planifier des tâches. Alors que la méthode Task.Factory.StartNew fonctionne comme une opération fork et est utilisée pour créer et démarrer de nouvelles tâches, la méthode Wait fonctionne comme une opération de jointure et attend que la tâche soit terminée.

L'extrait de code suivant illustre comment vous pouvez utiliser la méthode Task.Factory.StartNew.

Task.Factory.StartNew(() => TestMethod(), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);

Vous pouvez également créer une tâche à l'aide de la méthode Task.Run, comme indiqué dans l'extrait de code ci-dessous.

public async Task DoSomeWork()

        {

            await Task.Run(() => TestMethod());

        }

        void TestMethod()

        {

            Console.WriteLine("Hello world!");

        }

Si vous souhaitez renvoyer une valeur à partir d'une tâche, vous pouvez tirer parti de la méthode Task.FromResult comme indiqué dans l'extrait de code ci-dessous.

public async Task DoSomeWork()

   {

      string text = await Task.FromResult(GetMessage());

   }

private string GetMessage()

   {

      return "Hello world!";

   }

Vous pouvez également créer des tâches à l'aide d'un délégué ou d'une action. L'extrait de code suivant montre comment vous pouvez créer des tâches à l'aide d'actions et de délégués.

Task task1 = new Task (new Action(Display));

task1.Start();

Task task2 = new Task (delegate { Display(); });

task2.Start();

Vous pouvez également créer des tâches à l'aide de méthodes lamba et anonymes.

Task.Factory.StartNew et Task.Run

Task.Factory.StartNew est un moyen rapide de créer et de démarrer une tâche. Notez qu'un appel à Task.Factory.StartNew équivaut fonctionnellement à créer une instance de tâche, puis à appeler la méthode Start sur l'instance. Cependant, il n'est pas recommandé de l'utiliser pour de nombreuses raisons. Si vous souhaitez exécuter du code synchrone, Task.Factory.StartNew n'est pas un bon choix.

Notez que si un planificateur de tâches est disponible, la méthode StartNew exécutera la tâche sur ce planificateur de tâches. Au contraire, si un planificateur n'est pas disponible, il exécutera la tâche sur un thread de pool de threads. Il convient de noter que Task.Factory.StartNew utilise par défaut TaskScheduler.Current et non TaskScheduler.Default.

Notez qu'un appel à Task.Run (action) équivaut à l'instruction suivante: Task.Factory.StartNew(action, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

Au contraire, un appel à Task.Factory.StartNew (action) équivaut à l'instruction suivante:

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

Si vous souhaitez utiliser Task.Factory.StartNew si vous avez créé un planificateur de tâches personnalisé et lui transmettez explicitement l'instance du planificateur. Je recommanderais toujours d'utiliser Task.Run car il est beaucoup plus simple et a des valeurs par défaut plus sûres. En d'autres termes, nous devons éviter d'utiliser Task.Factory.StartNew sauf s'il est nécessaire de créer un planificateur de tâches, puis de le transmettre explicitement lors de l'appel de la méthode StartNew pour créer une nouvelle tâche et la planifier. Si vous deviez utiliser la méthode TaskFactory.StartNew de manière efficace et fiable, vous devez utiliser un planificateur de tâches personnalisé, puis spécifier CancellationToken et TaskCreationOptions.

Il est recommandé d'utiliser la méthode Task.Run lorsque vous n'avez pas besoin d'avoir un contrôle très fin sur la planification des threads et ses subtilités. Vous devez utiliser Task.Run principalement sur les méthodes liées au processeur. Cependant, vous devez utiliser Task.Run lors de l'appel de la tâche et non dans l'implémentation de la tâche. En d'autres termes, vous ne devez utiliser Task.Run pas dans une implémentation d'une méthode, mais au point où la méthode est appelée. À titre d'exemple, l'extrait de code suivant est un exemple de «mauvais» morceau de code.

public async Task DownloadDataFromWebAsync(Uri uri)

        {

            return await Task.Run(() =>

            {

                using (WebClient webClient = new WebClient())

                {

                    return webClient.DownloadString(uri);

                }

            });

        }

Reportez-vous à l'extrait de code ci-dessus. La méthode n'est pas évolutive car elle bloquerait le thread d'arrière-plan, récupérerait un thread du pool de threads et s'exécuterait de manière synchrone sur celui-ci. Par conséquent, cela consommerait plus de ressources dans votre système.