【问题标题】:Universal Windows UI Responsiveness with Async/Await具有 Async/Await 的通用 Windows UI 响应性
【发布时间】:2020-09-15 23:44:07
【问题描述】:

请帮助我了解如何正确等待长时间执行的任务,以使 UI 在通用 Windows 应用程序中保持响应。

在下面的代码中,OperateSystem 是一个继承 ObservableObject 的模型类。 OperateSystem.GetLatestDataFromAllDevices 连接到各种仪器、收集数据并使用来自仪器的信息更新类属性。视图使用来自操作系统的值进行更新。

在 dispatcher.RunAsync 任务运行时 UI 没有响应,我在 GetLatestDataFromAllDevices() 中添加了一个 Thread.Sleep(5000) 以确保它锁定 UI 5 秒。如果没有 await Task.Delay(refreshTimer),UI 永远不会更新(我假设它会在 UI 可以更新之前立即返回 GetLatestDataFromAllDevies)。将 refreshTimer 设置为 1ms 允许 UI 更新,但我知道这是另一个需要修复的问题的解决方法。

   public ProductionViewModel()
    {
        OperateSystem = new OperateSystem();
        StartButtonCommand = new RelayCommand(StartMeasurementSystem);
        StopButtonCommand = new RelayCommand(StopMeasurementSystem);

        if (!Windows.ApplicationModel.DesignMode.DesignModeEnabled)
        {
            dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
        }
    }

    private async void StartMeasurementSystem()
    {
            stopRequest = false;
            StopButtonEnabled = true;
            StartButtonEnabled = false;
            while (!stopRequest)
            {
                await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => OperateSystem.GetLatestDataFromAllDevices(ConfigurationSettingsInstance));
                await Task.Delay(refreshTimer);
            }
    }

在操作系统中

    internal void GetLatestDataFromAllDevices(ConfigurationSettings configurationSettings)
    {
        GetDataInstrument1(configurationSettings);
        GetDataInstrument2(configurationSettings);
        GetDataInstrument3(configurationSettings);
        GetDatainstrumetn4(configurationSettings);
    }

每个 GetDataInstrumnet 方法都连接到仪器、收集数据、执行一些缩放/格式化,并使用当前值更新类属性。

我关注了其他 S.O.使用 dispatcher.RunAsync 作为使用其他异步方法的答案我会得到线程编组错误。但现在我认为调度程序只是在 UI 线程上编组这些任务,所以它仍然会阻止 UI 更新。

为了重新创建线程编组错误,我将 GetLatestDataFromAllDevices 设为异步,并等待作为任务执行的方法。

    internal async void GetLatestDataFromAllDevices(ConfigurationSettings configurationSettings)
    {
        await Task.Run(()=>GetDataInstrument1(configurationSettings));
        GetDataInstrument2(configurationSettings);
        GetDataInstrument3(configurationSettings);
        GetDatainstrumetn4(configurationSettings);
    }

这会导致: System.Exception: '应用程序调用了一个为不同线程编组的接口。 (来自 HRESULT 的异常:0x8001010E (RPC_E_WRONG_THREAD))'

我已经进行了几次重构,但总是遇到线程编组错误或无响应的 UI,有什么好的方法可以完成这项工作?

【问题讨论】:

  • I added a Thread.Sleep(5000) - 那是正确的,因为工作是 scheduled on the UI thread,你用睡眠阻止了它。最好是the other way round。你也不能有意义地等待void
  • Stephen Cleary 在该线程上的回答是我所追求的(你的第一个链接)。公认的答案是让后台任务等待 UI 任务完成,Stephen 指出“如果你的 UI 是主”和后台线程是“奴隶”。对了!但是当我按照他的建议使用 Task.Run() 时,它在另一个线程(不是 UI 线程)上运行这些任务时,我得到线程编组错误......它还没有点击如何完成这个......
  • 我在使用 Task.Run() 进行线程编组时看到的问题是我正在更新绑定到我试图在另一个线程上运行的模型中的视图的属性吗?我可以为要显示的每个模型属性在视图模型上创建属性,然后在模型任务完成后触发一个方法来更新视图模型属性。这感觉像是很多多余的属性,但如果这是更清洁的方法,我完全赞成。还是还是没有点击...
  • 您在Task.Run 中运行非 UI 代码并返回数据供 UI 线程使用。来回切换将更难编码、更难调试且速度更慢。
  • Paulo 和 GSerg 感谢您的指点!在下面史蒂夫的回答中也注意到,我从我尝试 Task.Run(()=>...) 的任何方法中删除了任何 UI 属性更新,并且效果很好。我想我理解我试图从另一个线程更新 UI 线程上存在的属性。我可以在异步任务之外进行这些更新,因为它们不会阻塞。

标签: c# multithreading uwp async-await mvvm-light


【解决方案1】:

我已经进行了几次重构,但总是遇到线程编组错误或无响应的 UI,有什么好的方法可以完成这项工作?

由于您的 UI 没有响应,您必须将工作推送到后台线程(例如,Task.Run)。

对于将更新编组回 UI 线程,我建议(按优先顺序):

  1. 使用异步方法的返回值。例如,MyUiProperty = await Task.Run(() => MyBackgroundMethod(...));
  2. 使用Progress<T> 从异步方法中获取多个值。例如,var progress = new Progress<string>(update => MyUiProperty = update); await Task.Run(() => MyBackgroundMethod(..., progress));
  3. 在后台类中捕获SynchronizationContext 并使用它向 UI 线程发送更新。这是最不推荐的,因为它会导致您的后台驱动您的 UI,而不是相反。

【讨论】:

  • 谢谢斯蒂芬!我正在使用 await Task.Run(()=>.. 来收集我的问题。我还在更新 UI 在这些任务中绑定到的类属性。如果我了解正在发生的事情,那就是在后台更新属性线程,但视图绑定/可观察对象的工作都在 UI 线程上处理。我从 GetDataInstrument*() 方法中提取了对属性的任何更新,它无需绑定即可工作!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-14
  • 2017-10-26
  • 2019-06-13
  • 2015-09-30
  • 1970-01-01
相关资源
最近更新 更多