Reputation: 439
When trying to add data to an ObservableCollection, which is x:Binded in XAML to a ListView on the UI, I get this error:The application called an interface that was marshalled for a different thread (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))
My app implements MVVM. VieModel pulls data with an eventhandler. I have tried various SO and other solutions, such as:
var ignored = Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,() =>ViewList.Add(msg));
This gives the error: Current.Get == null
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.
I tried using DispatcherTimer, as accoring to: Updating ObservableCollection from non UI thread. I get the "WRONG_THREAD"
error message when trying to instantiate DispatcherTimer in the ViewModel, to access the UI thread:
disPatchTimer = new DispatcherTimer();
There is one suggestion that works, which many have upvoted here: 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?
Upvotes: 1
Views: 3488
Reputation: 457302
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.
Upvotes: 4
Reputation: 169400
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.
Upvotes: 4