web-dev-qa-db-fra.com

Comment passer l'indicateur LongRunning spécifiquement à Task.Run ()?

J'ai besoin d'un moyen de définir une tâche asynchrone aussi longue sans utiliser Task.Factory.StartNew (...) mais plutôt avec Task.Run (...) ou quelque chose de similaire.

Le contexte:

J'ai une tâche qui effectue une boucle continue jusqu'à ce qu'elle soit annulée en externe et que je souhaite définir comme "longue durée" (c'est-à-dire lui attribuer un thread dédié). Ceci peut être réalisé à l'aide du code ci-dessous:

var cts = new CancellationTokenSource();
Task t = Task.Factory.StartNew(
async () => {
while (true)
{
    cts.Token.ThrowIfCancellationRequested();
    try
    {
        "Running...".Dump();
        await Task.Delay(500, cts.Token);
    }
    catch (TaskCanceledException ex) { }
} }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

Le problème est que Task.Factory.StartNew (...) ne retourne pas la tâche async active transmise, mais plutôt une "tâche d'exécution de l'action" qui a toujours comme tâche taskStatus de "RanToCompletion". Étant donné que mon code doit pouvoir suivre l'état de la tâche pour voir quand elle devient "annulée" (ou "en défaut"), je dois utiliser l'une des méthodes suivantes:

var cts = new CancellationTokenSource();
Task t = Task.Run(
async () => {
while (true)
{
    cts.Token.ThrowIfCancellationRequested();
    try
    {
        "Running...".Dump();
        await Task.Delay(500, cts.Token);
    }
    catch (TaskCanceledException ex) { }
} }, cts.Token);

Task.Run (...), comme vous le souhaitez, renvoie le processus asynchrone lui-même, me permettant d'obtenir les statuts actuels de 'Cancelled' ou 'Faulted'. Je ne peux toutefois pas spécifier la tâche comme étant longue. Ainsi, tout le monde sait comment exécuter au mieux une tâche asynchrone tout en stockant cette tâche active elle-même (avec taskStatus souhaité) et en définissant la tâche sur longue durée?

14
Ryan

Appelez Unwrap sur la tâche renvoyée à partir de Task.Factory.StartNew. La tâche interne, dont le statut est correct, sera renvoyée.

var cts = new CancellationTokenSource();
Task t = Task.Factory.StartNew(
async () => {
while (true)
{
    cts.Token.ThrowIfCancellationRequested();
    try
    {
        "Running...".Dump();
        await Task.Delay(500, cts.Token);
    }
    catch (TaskCanceledException ex) { }
} }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
7
Ned Stoyanov

J'ai une tâche qui boucle en boucle jusqu'à ce qu'elle soit annulée en externe et que je souhaite définir comme "longue durée" (c'est-à-dire lui donner un thread dédié) ... tout le monde sait comment exécuter au mieux une tâche asynchique tout en stockant cette tâche active. lui-même (avec taskStatus souhaité) et en définissant la tâche à long terme?

Cela pose quelques problèmes. Premièrement, "long running" ne signifie pas nécessairement un thread dédié - cela signifie simplement que vous donnez à la TPL un indice que la tâche est longue. Dans l'implémentation actuelle (4.5), vous obtiendrez un thread dédié; mais ce n'est pas garanti et pourrait changer à l'avenir.

Donc, si vous avez besoin de un thread dédié, vous devrez simplement en créer un.

L'autre problème est la notion de "tâche asynchrone". Ce qui se passe réellement avec le code async exécuté sur le pool de threads, c’est que le thread est renvoyé au pool de threads alors que l’opération asynchrone (c.-à-d. Task.Delay) est en cours. Ensuite, lorsque l'opération asynchrone est terminée, un thread est pris dans le pool de threads pour reprendre la méthode async. Dans le cas général, cela est plus efficace que de réserver un thread spécifiquement pour effectuer cette tâche.

Ainsi, avec les tâches async exécutées sur le pool de threads, les threads dédiés n’ont pas vraiment de sens.


Concernant les solutions:

Si vous avez besoin un thread dédié pour exécuter votre code async, je vous recommanderais d'utiliser le AsyncContextThread de ma bibliothèque AsyncEx :

using (var thread = new AsyncContextThread())
{
  Task t = thread.TaskFactory.Run(async () =>
  {
    while (true)
    {
      cts.Token.ThrowIfCancellationRequested();
      try
      {
        "Running...".Dump();
        await Task.Delay(500, cts.Token);
      }
      catch (TaskCanceledException ex) { }
    }
  });
}

Cependant, vous n'avez presque certainement pas besoin d'un thread dédié. Si votre code peut s'exécuter sur le pool de threads, il devrait probablement le faire; et un thread dédié n'a pas de sens pour les méthodes async exécutées sur le pool de threads. Plus spécifiquement, l'indicateur de longue durée n'a pas de sens pour les méthodes async exécutées sur le pool de threads.

En d'autres termes, avec un async lambda, ce que le pool de threads réellement exécute (et considère comme des tâches) ne sont que les parties du lambda entre les instructions await. Étant donné que ces pièces ne durent pas longtemps, le drapeau de longue durée n'est pas requis. Et votre solution devient:

Task t = Task.Run(async () =>
{
  while (true)
  {
    cts.Token.ThrowIfCancellationRequested(); // not long-running
    try
    {
      "Running...".Dump(); // not long-running
      await Task.Delay(500, cts.Token); // not executed by the thread pool
    }
    catch (TaskCanceledException ex) { }
  }
});
21
Stephen Cleary

Sur un fil dédié, il n'y a rien à céder. N'utilisez pas async et await, utilisez des appels synchrones.

Cette question donne deux façons de faire un sommeil annulable sans await:

Task.Delay(500, cts.Token).Wait(); // requires .NET 4.5

cts.WaitHandle.WaitOne(TimeSpan.FromMilliseconds(500)); // valid in .NET 4.0 and later

Si une partie de votre travail utilise le parallélisme, vous pouvez démarrer des tâches parallèles, les enregistrer dans un tableau et utiliser Task.WaitAny sur le Task[]. Toujours pas d'utilisation de await dans la procédure du thread principal.

3
Ben Voigt

Cela n'est pas nécessaire et Task.Run suffira, car le planificateur de tâches définira une tâche sur LongRunning si son exécution dure plus de 0,5 seconde.

Voir ici pourquoi. https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html

Vous devez spécifier TaskCreationOptions personnalisé. Examinons chacune des Options. AttachedToParent ne doit pas être utilisé dans les tâches asynchrones, donc Est désactivé. DenyChildAttach doit toujours être utilisé avec les tâches asynchrones (Conseil: si vous ne le saviez pas déjà, StartNew n’est pas l’outil Dont vous avez besoin). DenyChildAttach est passé par Task.Run. HideScheduler Pourrait être utile dans certains scénarios de planification vraiment obscurs, mais en général, Devrait être évité pour les tâches asynchrones. Cela ne laisse que LongRunning et PreferFairness, qui sont deux astuces d'optimisation qui ne devraient être spécifiées que Après le profilage de l'application. Je vois souvent LongRunning mal utilisé En particulier. Dans la grande majorité des situations, le pool de threads va s'ajuster à une tâche de longue durée en 0,5 seconde, sans l'indicateur LongRunning. Très probablement, vous n'en avez pas vraiment besoin.

1
rolls