通用 Windows UI 响应 Async/Await

Universal Windows UI Responsiveness with Async/Await

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

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

UI 没有响应,而 dispatcher.RunAsync 任务是 运行,我向 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 方法都连接到仪器、收集数据、执行一些 scaling/formatting,并使用当前值更新 class 属性。

我关注了其他 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: 'The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))'

我在圈子里重构了几次,并保持 运行 线程编组错误或无响应 UI,完成此任务的好方法是什么?

I've refactored in circles a few times and keep running into either thread marshaling errors or an unresponsive UI, what's a good way to get this done?

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

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

  1. 使用异步方法的return值。例如,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 而不是相反。