【问题标题】:C# UWP XAML- Updating controls "synchronously"C# UWP XAML-“同步”更新控件
【发布时间】:2017-11-28 14:42:51
【问题描述】:

在这个问题中,我提供了一些链接,这些链接表明我已经完成了一些工作来寻找解决方案。

我正在开发一个带有触摸屏和 GPIO 的 UWP 应用。

UI 有一个停止按钮、一个重置按钮和一个文本块。 GPIO 用于一个物理启动按钮、一个电机和 2 个限位开关。电机可以旋转直到碰到限位开关。

控制硬件的代码(例如,Motor.Forward())已经编写和测试,但为简洁起见,本问题不包括在内。出于同样的原因,停止按钮的代码被排除在外。

如果这些方法中的步骤将同步执行...所需的行为可能由以下代码描述:

 //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";
 }

如果我没记错的话,“private void btnReset_Click()”中的 UI 更新可以工作,但它们不是同步的……也就是说,所有 UI 更新都在“btnReset_Click()”完成后立即完成。

From reading answers to similar questions... 似乎“Start()”中的 UI 更新失败,因为我不在 UI 线程上(“应用程序调用了一个为不同线程编组的接口。”)。 似乎任务异步模式是这类问题的常见答案。然而,我这样做的尝试产生了奇怪的结果......

下面的代码是我最接近预期结果的代码。我添加了使用 CoreDispatcher 处理 UI 更新的异步任务。

 //Task for updating the textblock in the UI
 private async Task UpdateText(string 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");
 }

上面代码的问题/特性(除了我可能因为刚开始使用 C# 而犯的任何初学者错误......以及它似乎过于复杂和令人困惑的事实):

-在“Start()”中,我在任务上使用 .Wait()(因为它似乎有效,我真的不明白为什么......),在 btnReset_Click() 中等待它们效果最好。 ..

-btnReset_Click() 不是同步的。 UI 更新似乎“落后了一步”......即,在调试模式下,当我跨过“await UpdateButton(resetButton,false)”时,停止按钮启用,当我跨过“await UpdateText(”Motor) 时,重置按钮禁用转动")"等等。

-关于 btnReset_Click()... while 循环实时持续时间超过 1 毫秒,但如果我删除所有“等待 Task.Delay(1)”,则 UI 更新将“落后一步”。包含“await Task.Delay(1)”后,UI 更新会“赶上”到应有的位置。为什么“await Task.Delay(1)”会以这种方式影响 UI 更新?

如果有知识渊博的人愿意解决这个问题的部分/全部,也许让我向他们询问有关他们答案的详细信息,我将不胜感激!

额外问题....我在触摸屏上还有一个“切换测试模式”按钮,它启用一个 UI 按钮列表并禁用另一个(基于静态布尔“testmode”)。 I don't need to use TAP to update the UI here,但请记住我想同步执行此操作(即使在此示例中似乎毫无意义)。

 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<Button> btns, enable)
 {
     foreach (var btn in btns)
     {
         await Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
             new DispatchedHandler(() => { btn.IsEnabled = enable; }));
     }
 }

正如上面所写,它的行为类似于 btnReset_Click()...,其中 UI 更新“落后了一步”。但是,如果我在事件处理程序中的每个等待中添加“.ConfigureAwait(false)”,那么它就会变成同步的。 I've done some reading on this topic, but don't fully understand it yet,我希望有更好理解的人来帮助我理解它,因为它与我的项目有关。

【问题讨论】:

    标签: c# xaml asynchronous uwp async-await


    【解决方案1】:

    你不应该做任何 Dispatcher.Run 和类似的事情......

    首先停下来思考并了解您的问题是什么以及 UI 不更新的原因。

    创建一个控制电机的新线程(与 UI 线程分开)。

    单击按钮时,调用电机线程上的方法。

    当电机线程上有需要更新 UI 的事件时,调用(同步?)UI 线程上的方法。

    【讨论】:

    • 感谢您抽出宝贵时间回复!您能否建议在哪里可以找到有关在 UWP 中创建线程的示例/信息?
    • **编辑:具体来说,我的意思是以您想到的任何方式创建线程...我问是因为我的理解是,线程创建在通用 Windows 平台应用程序中并不相同与其他 C# 程序,我还没有认识到任何与我的项目类似的 UWP 示例或材料:(
    【解决方案2】:

    简而言之,在构建应用时请考虑以下提示:

    • 切勿调用 .Wait()Result 或以其他方式尝试阻止 UI 调度程序线程上的异步操作
    • 如果您确实想创建工作线程来执行阻塞操作,请尝试await Task.Run(...),因为它很简单(您可以创建原始线程,但工作量更大)。对于像 while(notDone) ; // empty 这样的忙碌等待也是如此
    • 从后台线程(例如由Task.Run 创建的线程),如果您想更新用户界面,那么您可以使用Dispatcher.RunAsync(...),但只能设置用户界面的属性
    • 要在后台线程工作时禁用 UI,请设置 IsEnabled=false 或添加最顶部的半透明交互屏蔽等。

    另外,尝试从简单的事情开始(例如,没有硬件访问;只需使用Task.Delay(...).Wait() 来模拟硬件上的阻塞)。一旦 UI 基本正常工作,您就可以插入硬件调用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多