J'ai une application qui se connecte à une API de repos à l'aide des méthodes asynchronisées. J'ai cette configuration à l'aide d'Async / attendre à peu près partout qui se connecte à l'API, mais j'ai une question et un comportement étrange que je ne comprends pas complètement. Ce que je veux faire, c'est simplement renvoyer une licence dans certains scénarios lorsque le programme s'arrête. Ceci est initié par un événement de fermeture de fenêtre; Le gestionnaire d'événements est le suivant:
async void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { ...other synchronous code... //Check for floating licensing if (KMApplication.License != null && KMApplication.License.Scope != Enums.LicenseScope.Standalone) { for (int i = 0; i < 3; i++) { try { await KMApplication.License.ShutDown(KMApplication.Settings == null ? Enums.LicenseReturnModes.PromptOnShutdown : KMApplication.Settings.LicenseReturnMode) .ConfigureAwait(false); break; } catch (Exception ex) { _logger.Warn("Exception in license release, attempt " + i, ex); } } } await KMApplication.ApiService.Disconnect().ConfigureAwait(false); _logger.Info("Shutdown Complete"); Application.Current?.Shutdown(); }
3 Réponses :
Ok voici ce que j'ai fini par faire. Fondamentalement, la fermeture de la fenêtre commence une tâche qui attendra la libération et invoquera ensuite l'arrêt. C'est ce que j'essayais de faire auparavant, mais cela ne semblait pas travailler dans la méthode du vide ASYNC, mais cela semble être fait de cette façon. Voici le nouveau gestionnaire: et la méthode d'arrêt ressemble à ceci: p> espérons que cela aide quelqu'un. P> < p> Notez que c'est avec une application WPF définie sur shutdownmode = "onexplicitShutdown" code> dans app.xaml afin qu'il ne s'éteigne pas. l'application réelle jusqu'à ce que j'appelle l'arrêt. Si vous utilisez des winforms ou WPF est défini sur Arrêter sur la dernière fenêtre ou la fenêtre principale Fermer (la fenêtre principale Fermer est la valeur par défaut que je crois), vous vous retrouverez avec la condition de course décrite dans les commentaires ci-dessous et pourrez éteindre les threads avant les choses courent à l'achèvement. p> p>
Maintenant, vous avez une condition de course. Lorsque les sorties de l'application vont abandonner tous les threads de fond qui sont toujours en cours d'exécution et que les filets de filetage sont des filets de fond. En appelant task.run (async ...) code> vous avez chargé le travail final sur un fil de fil-piscine. Vous n'avez pas
attendre code> /
attendre code> la tâche, donc c'est une question de chance si votre finisseur remplira avant que l'application appelle
abandon code> sur le thread qui gère le finisseur.
Compris mais je ne pense pas ici ... C'est le gestionnaire d'arrêt de la fenêtre principale et, étant donné qu'il n'est pas annulé, la fenêtre disparaîtra, ce qui permettra à l'utilisateur de faire quoi que ce soit d'autre lorsque la tâche est en cours d'exécution. Essentiellement, cette tâche d'arrêt devrait être la dernière chose à courir et, étant donné que la méthode est attendue dans la tâche, elle attendra que cela complète avant d'appeler la fermeture de l'application. À ce stade, il ne devrait pas y avoir de fils de fond à craindre que je ne pense pas ... laissez-moi savoir si je suis hors de base quelque part.
Je suppose que vous ouvrez votre formulaire avec application.run (nouveau formulaire1 ()) code>. Voici ce que le Documentation dit à ce sujet: Cette méthode ajoute un gestionnaire d'événements au paramètre MainForm pour l'événement code> fermé code>. Le gestionnaire d'événements appelle
Quitterthread code> pour nettoyer l'application. I>
Ce ne sont pas des winforms, c'est WPF. App.xaml a startupuri = "mainwindow.xaml" code> et
shutdownmode = "onexplicitShutdown" code> donc non, cela ne devrait pas être arrêté à moins que je n'arrive pas à la fermeture. J'ai également vérifié cela en commentant la ligne de fermeture de l'application et la fenêtre disparaît, mais il reste le débogage jusqu'à ce que j'arrête le débogueur et que je ne ferme jamais (je n'ai pas commenté la ligne après la vérification).
C'est un bon point cependant. Je viens de modifier ma réponse pour clarifier que WPF doit être réglé sur l'arrêt explicite ou que vous obtenez cette condition de course.
Vous devriez probablement modifier votre question pour inclure la balise code> wpf code> aussi ...
Voici une version ASYNC de Le Formulant code>
événement. Il retarde la fermeture de la forme jusqu'à la fin de la tâche fournie code>. L'utilisateur est empêché de fermer la forme avant l'achèvement de la tâche.
OnformClosSaSync code> Evénement passe une version améliorée du
FormLOSEventargs code>
classe au code de manutention, avec deux propriétés supplémentaires: BOOL HIDEFORM code> et
int Délai d'attente code>. Ces propriétés sont en lecture / écriture, beaucoup comme l'existant Annuler Code> Propriété. Réglage
masquform code> à
true code> a pour effet de cacher le formulaire pendant que l'opération ASYNC est en cours, pour éviter de frustrer l'utilisateur. Réglage
Timeout code> à une valeur> 0 a pour effet d'abandonner l'opération ASYNC après la durée spécifiée dans MSEC et de la fermeture du formulaire. Sinon, il est possible que l'application puisse être laissée à pied indéfiniment avec une interface utilisateur cachée, ce qui pourrait certainement être un problème. La propriété
Annuler code> est toujours utilisable et peut être définie sur
true code> par le gestionnaire de l'événement, pour empêcher le formulaire de la fermeture. P>
Func<object, FormClosingAsyncEventArgs, Task> aggregator = null;
aggregator += Window_FormClosingAsync_A;
aggregator += Window_FormClosingAsync_B;
this.OnFormClosingAsync(aggregator);
Super merci. Je verrai si je peux adapter le concept à WPF mais je ne pense pas que cela devrait être si difficile. Accepter comme c'est une réponse plus approfondie que la mienne.
@sfaust mon plaisir! J'espère qu'il n'y a pas de bugs là-bas, car il y a beaucoup de scénarios couverts et je ne suis pas un grand fan de test unitaire. :-)
J'ai supprimé un appel à .result code>, cela pourrait provoquer des blocages.
Le nom que j'ai choisi pour l'événement ( FormClosSync code>) peut ne pas être très approprié. Le suffixe ASYNC désigne des membres asynchrones, c'est-à-dire qu'ils renvoient une tâche
code> ou un autre type en attente. Cet événement ne couvre pas ces critères. Peut-être que "Asyncaware" serait plus précis, car il accepte les gestionnaires de retournement de la tâche, mais cela est trop long pour un suffixe.
Le problème fondamental dans ce que vous essayez de faire est que Async / attendre suppose que le fil principal de l'application continue de fonctionner. Cette hypothèse est directement conflictuelle avec l'action d'arrêt, dont le travail consiste à mettre fin à toutes les tâches de fonctionnement. p>
Si vous examinez la documentation sur window_clusive code>
, il indique ce qui suit (et seulement les suivants suivants): p>
se produit directement après Fermer () est appelé, et peut être géré pour annuler la fermeture de la fenêtre. P> blockQuote>
Ceci est important. La seule chose que cela est censée faire est de vous permettre d'annuler par programmation la fermeture de la fenêtre, invitant ainsi une action supplémentaire d'utilisateur. P>
Vos attentes sont ignorées en raison de la fonctionnalité Async / Await. Async / attendre apparaît em> courir de manière linéaire; Cependant, ce qui se passe effectivement, c'est que le contrôle est transmis à l'appelant au premier
attendre code>. Le cadre suppose à ce stade que vous ne souhaitez pas annuler le formulaire près, et le programme est autorisé à résilier, en prenant toutes les autres actions avec elle. P>
Fondamentalement, tous les programmes de style C ont un point d'entrée principal, qui exécute une boucle. C'est ainsi que depuis les premiers jours de c et continue de cette manière dans WPF. Cependant, dans WPF, Microsoft a eu un peu intelligent et a décidé de cacher cela du programmeur. Il y a quelques options pour faire face aux choses qui doivent se produire après la fermeture de la fenêtre principale: p>
Re-abidez la boucle principale de votre programme et posez le code là-bas. Les détails sur la manière de faire cela peuvent être trouvés ici . p> li>
définir un Mode d'arrêt explicite et lancez la tâche pour initier cela. Call
Application. Arrêt () code>
comme toute dernière ligne de code que vous devez exécuter. P> li> ol>
Merci mais n ° 2 est essentiellement ce que je suis déjà venu avec l'aide de Theodor dans ma réponse ... évoquée pour l'explication détaillée, merci.
Il y a des avantages et des inconvénients avec votre approche. Avantages: En laissant la fenêtre près avant d'appeler la tâche Async, vous avez une chose de moins à craindre. Comme les minuteries frappent dans la fenêtre cachée et changent l'état interne. Inconvénients: il n'y a aucun moyen de retour. Si la tâche ASYNC échoue, vous ne pouvez pas annuler la fermeture de la fenêtre car elle a déjà fermé.
@Theodorzoulias - Vous semblez ne pas comprendre comment le cadre a été conçu pour fonctionner, et en particulier la manière dont ce gestionnaire d'événements a été conçu pour fonctionner. Le gestionnaire d'événements est censé être synchrone. Soit la forme est autorisée à fermer ou que ce n'est pas, et cela devrait être déterminable immédiatement. B> Tenir le formulaire ouvert via le gestionnaire d'événements n'est pas correct. S'il y a un désir de montrer un statut à l'utilisateur, alors vous (1) annuler le formulaire Fermer (2) Offrez la tâche d'arrière-plan que (3) affiche un type de préavis et (4) ferme le formulaire quand il est fini.
Ce que je comparais est une version ASYNC de l'événement code> code>, avec votre approche de tirer la tâche ASYNC après la fermeture du formulaire. Je comprends que vous ne pouvez pas utiliser le fichier de manière synchrone intégrée code> et vous attendre à ce qu'il fonctionne. C'était en fait le problème initial de l'OP, qui l'a motivé à poster cette question. J'ai fourni une implémentation de
FormClosSync code> pour Windows Formulaires dans Ma réponse .
Argumenter avec vous est inutile. Vous maquillé i> une opération asynchrone où il n'y en a pas. Cela ne fonctionne pas. Je ne sais pas pourquoi c'est difficile à obtenir, mais c'est là que nous sommes.
Je peux vous assurer que la solution que j'ai suggérée fonctionne. Si vous ne voulez pas le considérer comme une alternative à votre solution, ça va, je ne vais pas insister.
ASYNC VOID CODE> n'attendra pas que la méthode complète car il est
vide code> et non
tâche code>. Bien sûr, pour les événements de Winforms, vous ne pouvez pas utiliser
async tâche code>. Scénario probable: La méthode est tirée, puis votre application se ferme (car elle n'attend pas). Vous aurez probablement besoin de retarder l'arrêt pendant que cela complète.
Ouais depuis son gestionnaire d'événements, je ne peux pas le faire
Async Task Code> car il ne correspond pas à la signature qui est ce que j'ai du mal à me battre. Ma vraie confusion est qu'elle arrête effectivement que l'arrêt est appelé après la méthode du journal qui ne s'appelle jamais ...
Async flux sur un arrêt semble bizarre. Lorsque le premier ASYNC est initié, le fil de l'interface utilisateur redevient sur la manipulation de la pompe de message et ferme efficacement la fenêtre.
Avez-vous essayé d'annuler l'événement (via le
CleanEventargs code>)? Votre gestionnaire ferme explicitement l'application de toute façon. Vous devrez peut-être définir un drapeau pour vous assurer de ne pas entrer dans une boucle infinie.
Je n'avais pas essayé d'annuler l'événement mais je viens de le faire et pas de changement. Je suis d'accord que Async sur l'arrêt semble un peu étrange mais je ne sais pas comment aller d'autre chose à ce sujet. Je souhaite que le programme renvoie la licence lorsque l'utilisateur s'arrête, et c'est une méthode asynchratante car c'est un appel http pour le faire ...
Et si vous exécutez sans configurationAwait (FALSE) ou exécutez-le comme
KMAPPlication.licens .... getwaiter (). GetResult () code> (sans tâche) qui bloquera de manière synchrone.
Oui, c'est une impasse, du moins dans ce cas ...
Lorsque l'utilisateur essaie de fermer la fenêtre, quel est le comportement souhaité? Si la fenêtre se ferme immédiatement et que la finalisation des travaux se déroule dans le backgoound sans UI, ou si la fenêtre se ferme jusqu'à la finalisation du travail?
Répondu ci-dessous, mais la fenêtre devrait disparaître immédiatement (signaler à l'utilisateur que le programme est "fait"), puis il devrait finir de faire ce nettoyage final en arrière-plan avant qu'il ne soit complètement arrêté. Il ne devrait s'agir que quelques secondes que ce ne serait pas une grosse affaire si elle reste ouverte mais c'est mieux si elle s'en va tout de suite, ce que j'ai aussi loin que je le comprends.
Saviez-vous que l'application exécute simplement une boucle dans la méthode principale () et la fermeture de la fenêtre interrompt cette boucle, permettant ainsi au programme de quitter? Si vous mettez du code après la boucle, vous pouvez atteindre le comportement de manière synchrone.