问题
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