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?
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();
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) { }
}
});
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.
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.