Donc, quelque chose me dérange sur le nouveau support asynchrone en C # 5:
L'utilisateur appuie sur un bouton qui démarre une opération asynchrone. L'appel revient immédiatement et la pompe à messages recommence à fonctionner - c'est tout.
Ainsi, l'utilisateur peut appuyer à nouveau sur le bouton - provoquant une nouvelle entrée. Et si c'est un problème?
Dans les démos que j'ai vues, ils désactivent le bouton avant l' await
appel et le réactivent ensuite. Cela me semble être une solution très fragile dans une application réelle.
Faut-il coder une sorte de machine à états qui spécifie quels contrôles doivent être désactivés pour un ensemble donné d'opérations en cours? Ou existe-t-il une meilleure façon?
Je suis tenté de simplement afficher une boîte de dialogue modale pendant la durée de l'opération, mais cela ressemble un peu à l'utilisation d'un marteau.
Quelqu'un a des idées brillantes, s'il vous plaît?
ÉDITER:
Je pense que la désactivation des contrôles qui ne devraient pas être utilisés pendant l'exécution d'une opération est fragile car je pense que cela deviendra rapidement complexe lorsque vous aurez une fenêtre avec de nombreux contrôles dessus. J'aime garder les choses simples car cela réduit les risques de bugs, à la fois lors du codage initial et de la maintenance ultérieure.
Que se passe-t-il s'il existe une collection de contrôles qui doivent être désactivés pour une opération particulière? Et si plusieurs opérations s'exécutent simultanément?
la source
Réponses:
Commençons par noter que c'est déjà un problème, même sans support asynchrone dans la langue. Beaucoup de choses peuvent entraîner la suppression des messages pendant que vous gérez un événement. Vous devez déjà coder défensivement pour cette situation; la nouvelle fonctionnalité de langue rend cela plus évident , ce qui est la bonté.
La source la plus courante d'une boucle de message s'exécutant pendant un événement provoquant une rentrée indésirable est
DoEvents
. La bonne chose à propos de,await
par opposition à,DoEvents
est queawait
cela rend plus probable que les nouvelles tâches ne vont pas "affamer" les tâches en cours d'exécution.DoEvents
pompe la boucle de message, puis appelle de manière synchrone le gestionnaire d'événements rentrants, alors que la nouvelle tâche estawait
généralement mise en file d'attente afin qu'elle s'exécute à un moment donné dans le futur.Vous pouvez gérer cette complexité de la même manière que vous géreriez toute autre complexité dans un langage OO: vous enlevez les mécanismes dans une classe et rendez la classe responsable de la mise en œuvre correcte d'une stratégie . (Essayez de garder les mécanismes logiquement distincts de la politique; il devrait être possible de modifier la politique sans trop bricoler les mécanismes.)
Si vous avez un formulaire avec beaucoup de contrôles qui interagissent de manière intéressante, vous vivez dans un monde de complexité de votre propre fabrication . Vous devez écrire du code pour gérer cette complexité; si vous n'aimez pas cela, simplifiez le formulaire afin qu'il ne soit pas si complexe.
Les utilisateurs détesteront cela.
la source
Pourquoi pensez-vous que la désactivation du bouton avant le
await
puis la réactivation à la fin de l'appel est fragile? Est-ce parce que vous craignez que le bouton ne soit jamais réactivé?Eh bien, si l'appel ne revient pas, vous ne pouvez pas refaire l'appel, il semble donc que c'est le comportement que vous souhaitez. Cela indique un état d'erreur que vous devez corriger. Si votre appel asynchrone arrive à expiration, cela peut quand même laisser votre application dans un état indéterminé - là encore, un bouton activé pourrait être dangereux.
La seule fois où cela pourrait devenir compliqué, c'est s'il y a plusieurs opérations qui affectent l'état d'un bouton, mais je pense que ces situations devraient être très rares.
la source
Je ne sais pas si cela s'applique à vous, car j'utilise généralement WPF, mais je vois que vous faites référence à un
ViewModel
pour qu'il le puisse.Au lieu de désactiver le bouton, définissez un
IsLoading
indicateur surtrue
. Ensuite, tout élément d'interface utilisateur que vous souhaitez désactiver peut être lié au drapeau. Le résultat final est qu'il appartient à l'interface utilisateur de trier son propre état activé, et non votre logique métier.En plus de cela, la plupart des boutons dans WPF sont liés à un
ICommand
, et généralement je metsICommand.CanExecute
égal à!IsLoading
, donc il empêche automatiquement l'exécution de la commande lorsque IsLoading est égal à true (désactive également le bouton pour moi)la source
Il n'y a rien de fragile à cela, et c'est ce que l'utilisateur attend. Si l'utilisateur doit attendre avant d'appuyer à nouveau sur le bouton, faites-le attendre.
Je peux voir comment certains scénarios de contrôle pourraient rendre assez complexe la décision de désactiver / activer les contrôles, mais est-il surprenant que la gestion d'une interface utilisateur complexe soit complexe? Si vous avez trop d'effets secondaires effrayants à partir d'un contrôle particulier, vous pouvez toujours simplement désactiver le formulaire entier et lancer un gigantesque "chargement" sur le tout. Si c'est le cas sur chaque contrôle, votre interface utilisateur n'est probablement pas très bien conçue en premier lieu (couplage serré, mauvais regroupement des contrôles, etc.).
la source
La désactivation du widget est l'option la moins complexe et la moins sujette aux erreurs. Vous le désactivez dans le gestionnaire avant de démarrer l'opération asynchrone et vous le réactivez à la fin de l'opération. Ainsi, le désactiver + activer pour chaque contrôle sont conservés localement à la gestion de ce contrôle.
Vous pouvez même créer un wrapper, qui prendra un délégué et un contrôle (ou plusieurs d'entre eux), désactiver les contrôles et exécuter quelque chose de manière asynchrone, qui fera le délégué et que réactiver les contrôles. Il doit prendre en charge les exceptions (effectuez l'activation dans enfin), afin qu'il fonctionne toujours de manière fiable si l'opération échoue. Et vous pouvez le combiner avec l'ajout d'un message dans la barre d'état, afin que l'utilisateur sache ce qu'il attend.
Vous voudrez peut-être ajouter un peu de logique pour compter les désactivations, afin que le système continue de fonctionner si vous avez deux opérations, qui devraient toutes les deux en désactiver une troisième, mais ne s'excluent pas mutuellement. Vous désactivez le contrôle deux fois, il ne sera donc réactivé que si vous l'activez à nouveau deux fois. Cela devrait couvrir la plupart des cas tout en gardant les cas séparés autant que possible.
D'un autre côté, quelque chose comme la machine à états va devenir complexe. D'après mon expérience, toutes les machines d'état que j'ai jamais vues étaient difficiles à entretenir et votre machine d'état devrait englober tout le dialogue, reliant tout à tout.
la source
Bien que Jan Hudec et Rachel aient exprimé des idées connexes, je suggérerais d'utiliser quelque chose comme une architecture Model-View - exprimer l'état de la forme dans un objet et lier les éléments de forme à cet état. Cela désactiverait le bouton d'une manière qui peut évoluer pour des scénarios plus complexes.
la source