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?
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.
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.
User contributions licensed under CC BY-SA 3.0