Correct way to access UI from background thread in UWP

℡╲_俬逩灬. 提交于 2019-12-13 09:04:10

问题


I need to update an ObservableCollection that is x:Binded in XAML to a ListView. I can't find the proper functionality to access the UI thread, which is also linked to threading issues which I do not understand.

I get this error in various attempts:

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

I am implementing MVVM. The VieModel gets the data from the model into it's ObservableCollecton via a normal subscribed event - subscribing to an instance of the Models' ObservableCollection.CollectionChanged+= . The event calls a method which adds the Model data to the ViewModel's ObservableCollection, this is then what is binded to the UI's listview.

I have tried various Stack and other solutions:

var ignored = Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,() =>ViewList.Add(msg));

This gives the error: Current.Get == null.

I can't access Dispatcher directly, as it is being called in the ViewModel. Also no .Invoke or .BeginInvoke access that I could find which several solutions suggested.

Then I tried using DispatcherTimer, as accoring to: Updating ObservableCollection from non UI thread

This is where I start to think, its a different threading issue and I am doing something wrong which is more serious, as I even get the "WRONG_THREAD" error message when trying to instantiate DispatcherTimer in the ViewModel, to access the UI thread, here:

disPatchTimer = new DispatcherTimer();

There is however one suggestion that works, which many have upvoted: The application called an interface that was marshalled for a different thread - Windows Store App

Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
    {
        // Your UI update code goes here!
    }
);

Which does not, at least, look like an elegant solution the "creators" had in mind to properly use code.

I can't find any proper answers to the above, please help.


回答1:


What you should do is to inject your view model with an interface that has a RunOnUIThreadAsync method or similar.

You would then create a class that implements this interface and calls Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync in the UWP app where you can assume that the CoreApplicationView is always available.

In your unit tests against the view model where there is no CoreApplicationView, you could simply mock the interface.

The other option would be to look into the BindingOperations.EnableCollectionSynchronization API that lets you access the collection from multiple threads.




回答2:


The VieModel pulls data from the model into it's ObservableCollecton via a subscribed event.

You mention both "pull" and "event". Events are by nature "push" systems - the event is pushed to your code. However, there are some systems that produce asynchronous results via an event, so I assume that is what you're dealing with here since you specify the data is "pulled".

If this is correct, then the best solution is to first write a wrapper for the event-based asynchrony so it becomes task-based asynchrony. If you have a data service that looks like this:

class MyDataService
{
  public void DownloadData();
  public event MyDataArrivedEventHandler MyDataArrived;
}

then the wrapper would look something like this:

public static Task<MyData> GetMyDataAsync(this MyDataService service)
{
  var tcs = new TaskCompletionSource<MyData>();
  MyDataArrivedEventHandler handler = null;
  handler = (s,e) =>
  {
    service.MyDataArrived -= handler;
    if (e.Error != null) 
      tcs.TrySetException(e.Error);
    else 
      tcs.TrySetResult(e.Data);
  };
  service.MyDataArrived += handler;
  service.DownloadData();
  return tcs.Task;
}

Once you have a Task-based asynchronous pattern method, then consuming it and updating your viewmodel is straightforward:

// From UI thread.
var data = await service.GetMyDataAsync();
viewModel.AddRange(data); // or whatever

This approach allows you to use the context-capturing nature of await so that you don't have to do any thread transitions yourself.



来源:https://stackoverflow.com/questions/57522322/correct-way-to-access-ui-from-background-thread-in-uwp

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!