Universal Windows UI Responsiveness with Async/Await

0

Please help me understand how to properly await long executing tasks to keep the UI responsive in a Universal Windows application.

In the code below OperateSystem is a model class which inherits ObservableObject. OperateSystem.GetLatestDataFromAllDevices connects to a variety of instruments, collects data, and updates class properties with the information from the instruments. The views update with values from Operate System.

The UI is not responsive while the dispatcher.RunAsync task is running, I added a Thread.Sleep(5000) to GetLatestDataFromAllDevices() to make sure and it locks up the UI for 5 seconds. Without the await Task.Delay(refreshTimer) the UI never updates (I'm assuming it instantly goes back into the GetLatestDataFromAllDevies before the UI can update). Setting the refreshTimer to 1ms allows the UI to update, but I know that's a workaround for another issue that needs to be fixed.

   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);
            }
    }

In OperateSystem

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

Each of the GetDataInstrumnet methods connect to an instrument, gathers data, performs some scaling/formatting, and updates a class property with the current value.

I followed other S.O. answers to use the dispatcher.RunAsync as using other async methods I would get thread mashalling errors. But now I think the dispatcher is just marshaling these tasks on the UI thread anyway so it still blocks UI udpates.

To recreate the thread marshalling errors, I made GetLatestDataFromAllDevices async, and awaited a method executed as a task.

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

This results in: System.Exception: 'The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))'

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?

c#
multithreading
uwp
async-await
mvvm-light
asked on Stack Overflow May 28, 2020 by Singletrax

1 Answer

2

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?

Since you have an unresponsive UI, you must push the work to a background thread (e.g., Task.Run).

For marshalling updates back to the UI thread, I recommend (in order of preference):

  1. Using the return value of asynchronous methods. E.g., MyUiProperty = await Task.Run(() => MyBackgroundMethod(...));.
  2. Using Progress<T> to get multiple values from asynchronous methods. E.g., var progress = new Progress<string>(update => MyUiProperty = update); await Task.Run(() => MyBackgroundMethod(..., progress));.
  3. Capturing a SynchronizationContext in your background classes and using that for sending updates to the UI thread. This is the least recommended because it results in your background driving your UI instead of the other way around.
answered on Stack Overflow May 28, 2020 by Stephen Cleary

User contributions licensed under CC BY-SA 3.0