Ici, nous avons un Grid
avec un Button
. Lorsque l'utilisateur clique sur le bouton, une méthode dans une classe Utility est exécutée, ce qui force l'application à recevoir un clic sur Grid. Le flux de code doit s'arrêter ici et ne pas continuer tant que l'utilisateur n'a pas cliqué sur le Grid
.
J'ai déjà posé une question similaire ici:
Attendez que l'utilisateur clique sur C # WPF
Dans cette question, j'ai obtenu une réponse en utilisant async / wait qui fonctionne, mais comme je vais l'utiliser dans le cadre d'une API, je ne veux pas utiliser async / wait, car les consommateurs devront alors marquer leurs méthodes avec asynchrone dont je ne veux pas.
Comment écrire la Utility.PickPoint(Grid grid)
méthode pour atteindre cet objectif?
J'ai vu cela qui peut aider mais je ne l'ai pas bien compris pour s'appliquer ici pour être honnête:
Blocage jusqu'à la fin d'un événement
Considérez-la comme quelque chose comme la méthode Console.ReadKey () dans une application console. Lorsque nous appelons cette méthode, le flux de code s'arrête jusqu'à ce que nous entrions une valeur. Le débogueur ne continue pas tant que nous n'avons pas entré quelque chose. Je veux le comportement exact de la méthode PickPoint (). Le flux de code s'arrêtera jusqu'à ce que l'utilisateur clique sur la grille.
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid x:Name="View" Background="Green"/>
<Button Grid.Row="1" Content="Pick" Click="ButtonBase_OnClick"/>
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
// do not continue the code flow until the user has clicked on the grid.
// so when we debug, the code flow will literally stop here.
var point = Utility.PickPoint(View);
MessageBox.Show(point.ToString());
}
}
public static class Utility
{
public static Point PickPoint(Grid grid)
{
}
}
Aync/Await
savoir comment faire l'opération A et enregistrer cette opération ÉTAT maintenant que vous voulez que l'utilisateur clique sur la grille.AutoResetEvent
n'est pas ce que tu veux?var point = Utility.PickPoint(Grid grid);
dans la méthode Grid Click? faire une opération et retourner la réponse?Réponses:
Votre approche est fausse. L'événementiel ne signifie pas bloquer et attendre un événement. Vous n'attendez jamais, au moins vous essayez toujours de l'éviter. Attendre, c'est gaspiller des ressources, bloquer les threads et peut-être introduire le risque d'un blocage ou d'un thread zombie (au cas où le signal de libération ne serait jamais émis).
Il doit être clair que bloquer un thread pour attendre un événement est un anti-modèle car il contredit l'idée d'un événement.
Vous avez généralement deux options (modernes): implémentez une API asynchrone ou une API pilotée par les événements. Puisque vous ne voulez pas implémenter votre API asynchrone, vous vous retrouvez avec l'API événementielle.
La clé d'une API événementielle est qu'au lieu de forcer l'appelant à attendre un résultat de manière synchrone ou à interroger un résultat, vous laissez l'appelant continuer et lui envoyez une notification, une fois le résultat prêt ou l'opération terminée. Pendant ce temps, l'appelant peut continuer à exécuter d'autres opérations.
Lorsque vous examinez le problème du point de vue du thread, l'API événementielle permet au thread appelant, par exemple, le thread d'interface utilisateur, qui exécute le gestionnaire d'événements du bouton, d'être libre de continuer à gérer, par exemple, d'autres opérations liées à l'interface utilisateur, comme le rendu d'éléments d'interface utilisateur. ou gérer les entrées de l'utilisateur comme le mouvement de la souris et les pressions sur les touches. L'API événementielle a le même effet ou le même objectif qu'une API asynchrone, bien qu'elle soit beaucoup moins pratique.
Étant donné que vous n'avez pas fourni suffisamment de détails sur ce que vous essayez vraiment de faire, ce qui
Utility.PickPoint()
se passe réellement et le résultat de la tâche ou pourquoi l'utilisateur doit cliquer sur la `Grille, je ne peux pas vous offrir une meilleure solution . Je peux juste offrir un modèle général de la façon de mettre en œuvre votre exigence.Votre flux ou votre objectif est évidemment divisé en au moins deux étapes pour en faire une séquence d'opérations:
Grid
avec au moins deux contraintes:
Cela nécessite au moins deux notifications pour que le client de l'API autorise une interaction non bloquante:
Vous devez laisser votre API implémenter ce comportement et ces contraintes en exposant deux méthodes publiques et deux événements publics.
API utilitaire d'implémentation / refactorisation
Utility.cs
PickPointCompletedEventArgs.cs
Utilisez l'API
MainWindow.xaml.cs
MainWindow.xaml
Remarques
Les événements déclenchés sur un thread d'arrière-plan exécuteront leurs gestionnaires sur le même thread. L'accès
DispatcherObject
à un élément d'interface utilisateur similaire à partir d'un gestionnaire, qui est exécuté sur un thread d'arrière-plan, nécessite que l'opération critique soit mise en file d'attente à l'Dispatcher
aide de l'unDispatcher.Invoke
ou l' autre ouDispatcher.InvokeAsync
pour éviter les exceptions inter-thread.Lisez les remarques sur
DispatcherObject
pour en savoir plus sur ce phénomène appelé affinité de répartiteur ou affinité de thread.Quelques réflexions - répondez à vos commentaires
Parce que vous vous approchiez de moi pour trouver une "meilleure" solution de blocage, me donnant l'exemple des applications console, j'ai eu l'impression de vous convaincre, que votre perception ou votre point de vue est totalement faux.
Une application console est quelque chose de totalement différent. Le concept de filetage est un peu différent. Les applications de console n'ont pas d'interface graphique. Juste flux d'entrée / sortie / erreur. Vous ne pouvez pas comparer l'architecture d'une application console à une application GUI riche. Ça ne marchera pas. Vous devez vraiment comprendre et accepter cela.
Ne vous laissez pas tromper par les regards . Savez-vous ce qui se passe à l' intérieur
Console.ReadLine
? Comment est-il mis en œuvre ? Est-ce qu'il bloque le thread principal et en parallèle, il lit l'entrée? Ou s'agit-il simplement d'un sondage?Voici l'implémentation originale de
Console.ReadLine
:Comme vous pouvez le voir, c'est une opération synchrone simple . Il interroge l'entrée utilisateur dans une boucle "infinie". Pas de bloc magique et continuez.
WPF est construit autour d'un thread de rendu et d'un thread d'interface utilisateur. Ces threads tournent toujours afin de communiquer avec le système d'exploitation comme la gestion des entrées utilisateur - en gardant l'application réactive . Vous ne voulez jamais suspendre / bloquer ce thread car cela empêchera le framework de faire un travail d'arrière-plan essentiel, comme répondre aux événements de la souris - vous ne voulez pas que la souris se fige:
attente = blocage des threads = absence de réponse = mauvaise expérience utilisateur = utilisateurs / clients ennuyés = problèmes au bureau.
Parfois, le flux d'application nécessite d'attendre l'entrée ou l'exécution d'une routine. Mais nous ne voulons pas bloquer le thread principal.
C'est pourquoi les gens ont inventé des modèles de programmation asynchrones complexes, pour permettre d'attendre sans bloquer le thread principal et sans forcer le développeur à écrire du code multithreading compliqué et erroné.
Chaque framework d'application moderne propose des opérations asynchrones ou un modèle de programmation asynchrone, pour permettre le développement de code simple et efficace.
Le fait que vous vous efforcez de résister au modèle de programmation asynchrone, me montre un certain manque de compréhension. Chaque développeur moderne préfère une API asynchrone à une API synchrone. Aucun développeur sérieux ne se soucie d'utiliser le
await
mot - clé ou de déclarer sa méthodeasync
. Personne. Vous êtes le premier que je rencontre qui se plaint des API asynchrones et qui les trouve peu pratiques à utiliser.Si je vérifiais votre infrastructure, qui vise à résoudre les problèmes liés à l'interface utilisateur ou à faciliter les tâches liées à l'interface utilisateur, je m'attendrais à ce qu'elle soit asynchrone - tout le long.
L'API liée à l'interface utilisateur qui n'est pas asynchrone est un gaspillage, car cela compliquera mon style de programmation, donc mon code qui devient donc plus sujet aux erreurs et difficile à maintenir.
Une perspective différente: lorsque vous reconnaissez que l'attente bloque le thread d'interface utilisateur, crée une expérience utilisateur très mauvaise et indésirable car l'interface utilisateur se figera jusqu'à la fin de l'attente, maintenant que vous vous rendez compte, pourquoi offririez-vous un modèle d'API ou de plugin qui encourage un développeur à faire exactement cela - implémenter l'attente?
Vous ne savez pas ce que fera le plugin tiers et combien de temps une routine prendra jusqu'à ce qu'elle se termine. Il s'agit simplement d'une mauvaise conception de l'API. Lorsque votre API fonctionne sur le thread d'interface utilisateur, l'appelant de votre API doit pouvoir lui faire des appels non bloquants.
Si vous refusez la seule solution bon marché ou gracieuse, utilisez une approche événementielle comme indiqué dans mon exemple.
Il fait ce que vous voulez: démarrer une routine - attendre la saisie de l'utilisateur - poursuivre l'exécution - atteindre l'objectif.
J'ai vraiment essayé plusieurs fois d'expliquer pourquoi l'attente / le blocage est une mauvaise conception d'application. Encore une fois, vous ne pouvez pas comparer une interface utilisateur de console à une interface graphique riche, par exemple, la gestion des entrées à elle seule est une multitude plus complexe que la simple écoute du flux d'entrée. Je ne connais vraiment pas votre niveau d'expérience et où vous avez commencé, mais vous devriez commencer à adopter le modèle de programmation asynchrone. Je ne connais pas la raison pour laquelle vous essayez de l'éviter. Mais ce n'est pas du tout sage.
Aujourd'hui, les modèles de programmation asynchrones sont mis en œuvre partout, sur chaque plate-forme, compilateur, chaque environnement, navigateur, serveur, bureau, base de données - partout. Le modèle événementiel permet d'atteindre le même objectif, mais il est moins pratique à utiliser (s'abonner / se désabonner à / des événements, lire des documents (lorsqu'il y a des documents) pour en savoir plus sur les événements), en s'appuyant sur des threads d'arrière-plan. L'événementiel est désuet et ne doit être utilisé que lorsque les bibliothèques asynchrones ne sont pas disponibles ou ne s'appliquent pas.
En guise de remarque: le .NET Framwork (.NET Standard) offre la possibilité
TaskCompletionSource
(entre autres) de fournir un moyen simple de convertir une API existante pilotée par paires en une API asynchrone.Le comportement (ce que vous vivez ou observez) est très différent de la façon dont cette expérience est mise en œuvre. Deux choses différentes. Votre Autodesk utilise très probablement des bibliothèques asynchrones ou des fonctionnalités de langage ou un autre mécanisme de thread. Et c'est aussi lié au contexte. Lorsque la méthode qui vous vient à l'esprit s'exécute sur un thread d'arrière-plan, le développeur peut choisir de bloquer ce thread. Il a soit une très bonne raison de le faire, soit il a juste fait un mauvais choix de conception. Vous êtes totalement sur la mauvaise voie;) Le blocage n'est pas bon.
(Le code source d'Autodesk est-il open source? Ou comment savez-vous comment il est implémenté?)
Je ne veux pas vous offenser, croyez-moi. Mais veuillez reconsidérer pour implémenter votre API asynchrone. Ce n'est que dans votre tête que les développeurs n'aiment pas utiliser async / wait. Vous avez manifestement eu la mauvaise mentalité. Et oubliez cet argument d'application de console - c'est un non-sens;)
L'API liée à l'interface utilisateur DOIT utiliser async / wait chaque fois que possible. Sinon, vous laissez tout le travail pour écrire du code non bloquant au client de votre API. Vous me forceriez à envelopper chaque appel à votre API dans un fil d'arrière-plan. Ou pour utiliser une gestion d'événements moins confortable. Croyez-moi - chaque développeur décore ses membres plutôt
async
que de gérer des événements. Chaque fois que vous utilisez des événements, vous pouvez risquer une fuite de mémoire potentielle - cela dépend de certaines circonstances, mais le risque est réel et n'est pas rare lors d'une programmation imprudente.J'espère vraiment que vous comprenez pourquoi le blocage est mauvais. J'espère vraiment que vous déciderez d'utiliser async / wait pour écrire une API asynchrone moderne. Néanmoins, je vous ai montré une façon très courante d'attendre le non-blocage, en utilisant des événements, bien que je vous exhorte à utiliser async / wait.
Si vous ne souhaitez pas autoriser le plugin à accéder directement aux éléments de l'interface utilisateur, vous devez fournir une interface pour déléguer des événements ou exposer des composants internes via des objets abstraits.
L'API s'abonne en interne aux événements d'interface utilisateur au nom du complément, puis délègue l'événement en exposant un événement «wrapper» correspondant au client API. Votre API doit proposer des crochets sur lesquels le complément peut se connecter pour accéder à des composants d'application spécifiques. Une API de plugin agit comme un adaptateur ou une façade pour donner aux externes un accès aux internes.
Pour permettre un certain degré d'isolement.
Jetez un œil à la façon dont Visual Studio gère les plugins ou nous permet de les implémenter. Imaginez que vous souhaitez écrire un plugin pour Visual Studio et faites des recherches sur la façon de procéder. Vous vous rendrez compte que Visual Studio expose ses composants internes via une interface ou une API. EG, vous pouvez manipuler l'éditeur de code ou obtenir des informations sur le contenu de l'éditeur sans y avoir réellement accès.
la source
var str = Console.ReadLine(); Console.WriteLine(str);
Que se passe-t-il lorsque vous exécutez l'application en mode débogage. Il s'arrêtera à la première ligne de code et vous forcera à entrer une valeur dans l'interface utilisateur de la console, puis après avoir entré quelque chose et appuyé sur Entrée, il exécutera la ligne suivante et imprimera réellement ce que vous avez entré. Je pensais exactement au même comportement mais dans l'application WPF.Personnellement, je pense que tout le monde est trop compliqué, mais peut-être que je ne comprends pas bien la raison pour laquelle cela doit être fait d'une certaine manière, mais il semble qu'un simple contrôle booléen puisse être utilisé ici.
D'abord et avant tout, rendez votre grille testable en définissant les propriétés
Background
etIsHitTestVisible
, sinon elle ne capturera même pas les clics de souris.Créez ensuite une valeur booléenne qui peut stocker si l'événement "GridClick" doit se produire. Lorsque la grille est cliquée, vérifiez cette valeur et effectuez l'exécution à partir de l'événement de clic de la grille s'il attend le clic.
Exemple:
la source
J'ai essayé quelques trucs mais je ne peux pas m'en passer
async/await
. Parce que si nous ne l'utilisons pas, cela provoqueDeadLock
ou l'interface utilisateur est bloquée et nous sommes alors autorisés à prendre desGrid_Click
entrées.la source
Vous pouvez bloquer de manière asynchrone en utilisant un
SemaphoreSlim
:Vous ne pouvez pas, et vous ne voulez pas non plus, bloquer le thread du répartiteur de manière synchrone, car il ne pourra jamais gérer le clic sur le
Grid
, c'est-à-dire qu'il ne pourra pas être bloqué et gérer les événements en même temps.la source
Console.Readline
bloque , c'est-à-dire qu'il ne revient pas tant qu'une ligne n'a pas été lue. VotrePickPoint
méthode ne fonctionne pas. Il revient immédiatement. Cela pourrait potentiellement se bloquer, mais vous ne pourrez pas gérer les entrées d'interface utilisateur pendant ce temps, comme je l'ai écrit dans ma réponse. En d'autres termes, vous devrez gérer le clic à l'intérieur de la méthode pour obtenir le même comportement.PickPoint
qui gère les événements de la souris. Je ne vois pas où tu vas avec ça?Techniquement, c'est possible avec
AutoResetEvent
et sansasync/await
, mais il y a un inconvénient important:L'inconvénient: si vous appelez cette méthode directement dans un gestionnaire d'événements de bouton comme le fait votre exemple de code, un blocage se produira et vous verrez que l'application cesse de répondre. Étant donné que vous utilisez le seul thread d'interface utilisateur pour attendre le clic de l'utilisateur, il ne peut pas répondre à l'action de n'importe quel utilisateur, y compris le clic de l'utilisateur sur la grille.
Les consommateurs de la méthode doivent l'invoquer dans un autre thread pour éviter les blocages. Si cela peut être garanti, c'est bien. Sinon, vous devez appeler la méthode comme ceci:
Cela peut causer plus de problèmes aux consommateurs de votre API, sauf qu'ils géraient leurs propres threads. Voilà pourquoi a
async/await
été inventé.la source
Je pense que le problème concerne le design lui-même. Si votre API fonctionne sur un élément particulier, elle doit être utilisée dans le gestionnaire d'événements de cet élément, et non sur un autre élément.
Par exemple, ici, nous voulons obtenir la position de l'événement click sur la grille, l'API doit être utilisée dans le gestionnaire d'événements associé à l'événement sur l'élément Grid, pas sur l'élément button.
Maintenant, si l'exigence est de gérer le clic sur la grille uniquement après avoir cliqué sur le bouton, la responsabilité du bouton sera d'ajouter le gestionnaire d'événements sur la grille, et l'événement de clic sur la grille affichera la boîte de message et supprimera ce gestionnaire d'événements ajouté par le bouton pour qu'il ne se déclenche plus après ce clic ... (pas besoin de bloquer le Thread UI)
Juste pour dire que si vous bloquez le thread d'interface utilisateur sur le clic du bouton, je ne pense pas que le thread d'interface utilisateur sera en mesure de déclencher l'événement de clic sur la grille par la suite.
la source
Tout d'abord, le thread d'interface utilisateur ne peut pas être bloqué, tout comme la réponse que vous avez obtenue de votre première question.
Si vous êtes d'accord avec cela, éviter l'asynchronisation / attendre pour que votre client fasse moins de modifications est faisable, et n'a même pas besoin de multi-threading.
Mais si vous voulez bloquer le thread d'interface utilisateur ou le "flux de code", la réponse sera que c'est impossible. Parce que si le thread d'interface utilisateur a été bloqué, aucune autre entrée ne peut être reçue.
Étant donné que vous avez parlé de l'application console, je fais juste une explication simple.
Lorsque vous exécutez une application console ou appelez à
AllocConsole
partir d'un processus qui ne s'est attaché à aucune console (fenêtre), conhost.exe qui peut fournir une console (fenêtre) sera exécuté et une application console ou un processus d'appel sera attaché à la console ( fenêtre).Donc, tout code que vous écrivez qui pourrait bloquer le thread de l'appelant tel que
Console.ReadKey
ne bloquera pas le thread de l'interface utilisateur de la fenêtre de la console, tout cela est la raison pour laquelle l'application de la console attend votre entrée mais peut toujours répondre à une autre entrée comme un clic de souris.la source