Le traitement séquentiel des tâches asynchrones
Assumer suivants du code synchrone:
try
{
Foo();
Bar();
Fubar();
Console.WriteLine("All done");
}
catch(Exception e) //For illustration purposes only. Catch specific exceptions!
{
Console.WriteLine(e);
}
Supposons maintenant que toutes ces méthodes ont un Async homologue et je dois utiliser ceux pour une raison quelconque, il suffit d'enrouler le tout dans une nouvelle tâche n'est pas une option.
Comment puis-je obtenir le même comportement?
Ce que je veux dire par "même" est:
- Exécuter un gestionnaire pour l'exception, si l'on est jeté.
- Arrêter l'exécution de l'une des méthodes suivantes, si une exception est levée.
La seule chose que j'ai été en mesure de venir avec est horrible:
var fooTask = FooAsync();
fooTask.ContinueWith(t => HandleError(t.Exception),
TaskContinuationOptions.OnlyOnFaulted);
fooTask.ContinueWith(
t =>
{
var barTask = BarAsync();
barTask.ContinueWith(t => HandleError(t.Exception),
TaskContinuationOptions.OnlyOnFaulted);
barTask.ContinueWith(
t =>
{
var fubarTask = FubarAsync();
fubarTask.ContinueWith(t => HandleError(t.Exception),
TaskContinuationOptions.OnlyOnFaulted);
fubarTask.ContinueWith(
t => Console.WriteLine("All done"),
TaskContinuationOptions.OnlyOnRanToCompletion);
},
TaskContinuationOptions.OnlyOnRanToCompletion);
},
TaskContinuationOptions.OnlyOnRanToCompletion);
Veuillez noter:
- J'ai besoin d'une solution qui fonctionne avec .NET 4, donc
async/await
est hors de question. Toutefois, si cela fonctionnerait avecasync/await
hésitez pas à montrer comment. - Je n'ai pas besoin d'utiliser le TPL. Si c'est impossible, avec les TPL une autre approche serait OK, peut-être avec le Réactif Extensions?
- Ne crois pas que ça serait possible d'écrire une méthode d'assistance que prend un
params Action[]
paramètre avec une complète/gestionnaire d'erreur à partir de laquelle il construit toute cette méchanceté pour vous? - Bonne idée. C'est ce que je vais faire si il n'y a pas de "vrai" solution...
- Vous devez également ajouter une poursuite pour chaque tâche de gérer
Canceled
, si l'une des tâches sont annulables. - Supposons qu'ils ne peuvent pas être annulées.
- Ce ne serait pas une
Action[]
, il serait unTask[]
. Fondamentalement, unForEachAsync
qui intègre la gestion des exceptions. - Non, ce qui serait en fait un
Func<Task>[]
, parce que les tâches ne doit être démarré lorsque la précédente a été un succès. - Ouais.
- Ouais, je suppose; je viens de comprendre le "helper" méthode ne se soucient pas et il suffit de passer dans n'importe quelle action/lambda appel et ne l'
Task
trucs pour vous en interne, surtout si vous ne voulez pas que les appelants externes pour ajouter peluches/trucs à la tâcheContinueWith
ou d'autres membres. (cela pourrait être utile aussi bien, pourrait juste avoir des surcharges pour les deux) - Cela semble juste comme un récit alambiqué de la façon de faire de vos méthodes asynchrones synchrone. Si c'est le cas, pourquoi les faire asynchrone pour commencer?
- Il aurait besoin d'être un
Func<Task>
parce que la méthode d'assistance des besoins de la tâche, pour être en mesure de relier les fils de la poursuite. Sans que vous auriez besoin d'envelopper le travail dans unStartNew
, et qui serait mauvais que vous êtes inutilement bloquer un thread du pool pour la durée. - Ils sont orthogonaux concepts. C'est en fait une commune et appropriée en cas d'utilisation d'exécuter un certain nombre d'opérations asynchrones de manière séquentielle. L'idée est que vous n'êtes pas en gardant un fil occupé par l'avoir fais un blocage d'attente sur chaque opération. C'est l'ensemble de la prémisse de la 5.0
async/await
fonctionnalité. - Pas synchrone. Juste séquentielle.
- Comment est-ce différent de l'exécution de la trois méthodes synchrones sur un seul thread d'arrière-plan, si? Il n'y a pas de blocage d'attente dans les deux cas.
- Comme je l'ai dit dans la question, je ne peux pas le faire. La raison la plus simple pour cela est que l'API, je suis en utilisant uniquement les offres de méthodes Asynchrones.
- Malheureusement avec 4.0 j'ai été obligé de faire essentiellement la même chose que vous avez là-haut. J'ai ajouté une méthode d'aide à nettoyer le fil de code, mais ouais, c'est moche.
- Vous êtes en supposant que les tâches représentent toutes des CPU de travail. C'est seulement un sous-ensemble des opérations asynchrones. Vous devez également tenir compte de async IO comme un autre grand exemple. Alors que cette tâche est de "lancer" aucun fil ne doit être en cours d'exécution sur tous les.
- J'espère que vous êtes juste le réglage de votre réponse. Je ne pense pas qu'il devrait être supprimé, il est une solution de contournement possible.
- Je comprends maintenant. Merci pour l'explication. J'ai quelques idées sur la façon de résoudre ce problème. Je vais voir si je peut pas les transformer en quelque chose de tangible, plus tard ce soir. (Aussi lol @ les gens se plaignent d'être coincé avec .NET 4.0. Nos développeurs refusent de se servir de quelque chose du passé .NET 3.5 ici).
- Ouais j'ai fait, surtout parce que je pensais que c'était incorrect du fait qu'il ne prévoit pas le comportement souhaité, mais j'ai modifié et restauré en elle.
Vous devez vous connecter pour publier un commentaire.
Voici comment cela pourrait fonctionner avec
async
:Ce serait de travailler sur .NET 4.0 si vous avez installé (version préliminaire) Microsoft.Bcl.Async paquet.
Puisque vous êtes coincé sur VS2010, vous pouvez utiliser une variante de Stephen Toub est
Puis
:Vous pouvez l'utiliser en tant que tel:
À l'aide Rx, il devrait ressembler à ceci (en supposant que vous n'avez pas le
async
méthodes déjà exposé commeIObservable<Unit>
):Je pense. Je ne suis pas un Rx maître, par tous les moyens. 🙂
Then
méthode d'extension pour plus de naturel séquentielle chaînage de méthodes asynchrones.Juste par souci d'exhaustivité, que je voudrais mettre en œuvre la méthode d'assistance proposé par Chris Sinclair:
Cela garantit que chaque tâche ultérieure n'est demandée que lorsque la précédente est terminée avec succès.
Il suppose que le
Func<Task>
renvoie à une déjà en cours d'exécution de la tâche.Ce que vous avez ici est essentiellement un
ForEachAsync
. Vous souhaitez exécuter chaque async élément, de manière séquentielle, mais avec une gestion d'erreur de soutien. Voici un exemple de mise en œuvre:J'ai ajouté le support pour annulé des tâches en tant que bien, juste pour être plus général, et parce qu'il a si peu à faire.
Il ajoute chaque tâche comme une continuation de la tâche précédente, et tout le long de la ligne, il s'assure que toutes les exceptions résultat de la tâche finale de l'exception de jeu.
Voici un exemple d'utilisation:
Task<Task> continuation = currentTask.ContinueWith(t => { if (t.IsFaulted || t.IsCanceled) { return t; } return function(); });
Vous devriez être en mesure de créer une méthode pour combiner les deux tâches, et de ne commencer la deuxième si la première réussit.
vous pouvez ensuite la chaîne de tâches:
Si vos tâches sont démarrés immédiatement, vous pouvez juste envelopper dans un
Func
:Func<Task>
au lieu de cela, alors vous pouvez faireFooAsync().Then(BarAsync).Then(FubarAsync)
.Maintenant, je n'ai pas vraiment utilisé le TPL beaucoup, donc c'est juste un coup de poignard dans le noir. Et compte tenu de ce que @Servy mentionné, peut-être que cela ne fonctionne pas complètement asynchrone. Mais j'ai pensé à la poster, et si c'est façon au large de la marque, vous pouvez downvote moi pour l'oubli ou je peux l'avoir supprimé (ou nous pouvons simplement de corriger ce que les besoins de fixation)
Et il aurait d'utilisation comme:
Pensées? Downvotes? 🙂
(Je préfère largement async/await si)
EDIT: en Fonction de vos commentaires à prendre
Func<Task>
, serait-ce une mise en œuvre correcte?params Func<Task>[]
et de travailler avec cela.Task
au lieu deAction
?RunAsync
, ce qui est exactement ce que j'ai ne pas vous voulez.Func<Task>
. Que la fonction peut retourner une déjà en cours d'exécution de la tâche, ou de démarrer une nouvelle tâche et de le retourner.