C # UWP XAML – Mise à jour des contrôles «de manière synchrone»

Tout au long de cette question, j’ai inclus des liens qui montrent que j’ai déjà travaillé à la recherche d’une solution.

Je développe une application UWP avec écran tactile et GPIO.

L’interface utilisateur a un bouton d’arrêt, un bouton de réinitialisation et un bloc de texte. GPIO est utilisé pour un bouton de démarrage physique, un moteur et 2 fins de course. Le moteur peut tourner jusqu’à ce qu’il se heurte à un fin de course.

Un code pour contrôler le matériel (par exemple, Motor.Forward ()) a été écrit et testé, mais est exclu de cette question pour des raisons de brièveté. Le code du bouton d’arrêt est exclu pour la même raison.

Si les étapes de ces méthodes fonctionnent de manière synchrone … le comportement souhaité peut être décrit par le code suivant:

//Event handler for when physical start button is pushed private async void StartButtonPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args) { Start(); } private void Start() { //Update UI stopButton.IsEnabled = true; resetButton.IsEnabled = false; textBlock.Text = "Motor turning"; Motor.Forward(); while(Motor.HasNotHitEndLimit()) Motor.Off(); //Update UI stopButton.IsEnabled = false; resetButton.IsEnabled = true; textBlock.Text = "Task complete"; } //Event handler for reset button private void btnReset_Click() { //Update UI stopButton.IsEnabled = true; resetButton.IsEnabled = false; textBlock.Text = "Motor turning"; Motor.Back(); while(Motor.HasNotHitStartLimit()) Motor.Off(); //Update UI stopButton.IsEnabled = false; resetButton.IsEnabled = true; textBlock.Text = "Reset complete"; } 

Si je me souviens bien, les mises à jour de l’interface utilisateur au sein de “private void btnReset_Click ()” fonctionnent, mais elles ne sont pas synchrones … Autrement dit, toutes les mises à jour de l’interface utilisateur étaient terminées juste après “btnReset_Click ()”.

De la lecture des réponses à des questions similaires … il semble que les mises à jour de l’interface utilisateur dans “Start ()” échouent, car je ne suis pas sur le thread d’interface utilisateur (“L’application a appelé une interface qui a été organisée pour un autre thread.”). Il semble que le motif asynchrone de tâche soit une réponse courante à ce type de questions. Cependant, mes tentatives en ce sens ont donné des résultats étranges …

Le code ci-dessous est le plus proche du résultat souhaité. J’ai ajouté des tâches asynchrones utilisant CoreDispatcher pour gérer les mises à jour de l’interface utilisateur.

  //Task for updating the textblock in the UI private async Task UpdateText(ssortingng updateText) { await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() => { textBlock.Text = updateText; })); } //Task for enable/disable a given button private async Task UpdateButton(Button btn, bool shouldThisBeEnabled) { await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(() => { btn.IsEnabled = shouldThisBeEnabled; })); } //Event handler for when physical start button is pushed private async void StartButtonPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args) { Start(); } private void Start() { //Update UI UpdateButton(stopButton,true).Wait(); UpdateButton(resetButton,false).Wait(); UpdateText("Motor turning").Wait(); Motor.Forward(); while(Motor.HasNotHitEndLimit()) Task.Delay(1).Wait(); Motor.Off(); //Update UI UpdateButton(stopButton,false).Wait(); UpdateButton(resetButton,true).Wait(); UpdateText("Task complete").Wait(); } //Event handler for reset button private async void btnReset_Click() { //Update UI await UpdateButton(stopButton,true); await UpdateButton(resetButton,false); await UpdateText("Motor turning"); await Task.Delay(1); Motor.Back(); while(Motor.HasNotHitStartLimit()) await Task.Delay(1); Motor.Off(); //Update UI await UpdateButton(stopButton,false); await UpdateButton(resetButton,true); await UpdateText("Reset complete"); } 

Problèmes / idiosyncrasies avec le code ci-dessus (à part les erreurs de débutant que je pourrais faire en raison du fait que je commence juste avec C # … et du fait que cela semble trop compliqué et déroutant):

-Dans “Start ()” j’utilise .Wait () sur les tâches (parce que cela semble fonctionner, je ne comprends pas vraiment pourquoi …), et dans btnReset_Click (), il a été préférable de les attendre …

-btnReset_Click () n’est pas synchrone. Les mises à jour de l’interface utilisateur semblent être “un pas en arrière” … C’est-à-dire qu’en mode débogage, le bouton d’arrêt est activé lorsque je passe au-dessus de “wait UpdateButton (resetButton, false)”, le bouton de réinitialisation est désactivé lorsque j’interromps “attendent UpdateText (” Moteur tournant “)”, et ainsi de suite.

-Regarding btnReset_Click () … La boucle while dure BEAUCOUP plus de 1 milliseconde en temps réel, et pourtant, si je supprime tout “wait Task.Delay (1)”, les mises à jour de l’interface utilisateur sont “un pas en arrière”. Avec “wait Task.Delay (1)” inclus, les mises à jour de l’interface utilisateur sont “rattrapées” là où elles devraient être. Pourquoi “wait Task.Delay (1)” affecte-t-il les mises à jour de l’interface utilisateur de cette manière?

Si des personnes bien informées sont disposées à répondre à tout ou partie de cette question et me laisserons peut-être leur demander des précisions sur leurs réponses, je vous en serais très reconnaissant!

Question bonus …. J’ai également un bouton “Basculer le mode de test” sur l’écran tactile qui active une liste de boutons de l’interface utilisateur et en désactive un autre (basé sur un “testmode” statique de bool.). Je n’ai pas besoin d’utiliser TAP pour mettre à jour l’interface utilisateur ici , mais rappelez-vous que je veux le faire de manière synchrone (même si cela semble inutile dans cet exemple).

  private async void btnTestMode_Click(object sender, RoutedEventArgs e) { testMode = !testMode; if (testMode == true) { await UpdateButtons(TestModeButtons,true); await UpdateButtons(NormalModeButtons,false); return; } await UpdateButtons(TestModeButtons,true); await UpdateButtons(NormalModeButtons,false); } private async Task UpdateButtons(List

Comme il est écrit ci-dessus, cela se comporte comme btnReset_Click () … où les mises à jour de l’interface utilisateur sont “un pas en arrière”. Cependant, si j’ajoute “.ConfigureAwait (false)” à chaque attente dans le gestionnaire d’événements, il devient alors synchrone. J’ai lu un peu sur ce sujet, mais je ne le comprends pas encore tout à fait et j’aimerais beaucoup quelqu’un qui comprend mieux pour m’aider à le comprendre en ce qui concerne mon projet.

Vous ne devriez pas faire cela, ni Dispatcher.Run …

Commencez par arrêter, réfléchissez et comprenez quel est votre problème et pourquoi l’interface utilisateur ne se met pas à jour.

Créez un nouveau thread où vous contrôlez vos moteurs (séparé du thread de l’interface utilisateur).

Sur le clic de bouton, appelez la méthode sur le fil de vos moteurs.

Lorsqu’il existe des événements sur le thread moteurs pour lesquels vous devez mettre à jour l’interface utilisateur, appelez des méthodes (de manière synchrone?) Sur le thread UI.

En résumé, tenez compte de ces conseils lors de la création de votre application:

  • .Wait() jamais .Wait() ou Result ou essayez autrement de bloquer une opération asynchrone sur un thread de dissortingbuteur d’interface utilisateur
  • Si vous souhaitez créer des threads de travail pour effectuer des opérations de blocage, essayez d’ await Task.Run(...) pour sa simplicité (vous pouvez créer des threads bruts mais cela await Task.Run(...) plus de travail). Idem pour les temps d’attente chargés comme while(notDone) ; // empty while(notDone) ; // empty
  • À partir d’un thread d’arrière-plan (tel que celui créé par Task.Run ), si vous souhaitez mettre à jour l’interface utilisateur, utilisez Dispatcher.RunAsync(...) mais uniquement pour définir les propriétés de votre interface utilisateur.
  • Pour désactiver votre interface utilisateur pendant qu’un thread d’arrière-plan fonctionne, définissez IsEnabled=false ou ajoutez un bouclier d’interaction emi-transparent le plus haut, etc.

Essayez également de commencer avec quelque chose de simple (par exemple, pas d’access au matériel; utilisez simplement Task.Delay(...).Wait() pour simuler le blocage sur le matériel). Une fois que l’interface utilisateur fonctionne, vous pouvez twigr les appels matériels.