Image this: you’re working on a Silverlight application, using the MVVM pattern, and you want to do something with the result of an async request (for example, you’re fetching persons, and you want to add them to a list in your VM when the operation completes). Something like this:
PersonServiceReference.PersonServiceClient client =
new PersonServiceReference.PersonServiceClient(); client.LoadPersonsCompleted += (s, a) => { if (a.Error == null) { AllPersons = new ObservableCollection<Person>(a.Result); } }; client.LoadPersonsAsync();
However, when you do this, you get an “invalid cross-thread exception” (if AllPersons is bound to a list in your UI). What does this mean? Simply put, you’re trying to access the UI thread (as the list you’re manipulating is bound to something in your UI), but you’re doing this from another thread: the thread on which your async operation runs.
Luckily, this is pretty easy to solve: invoke the Dispatcher as such and you’ll get rid of above exception:
Dispatcher.BeginInvoke(() =>
{
// your code
});
Now, if you’re trying this at the moment, and you’re using the MVVV pattern, you’re probably trying this in your VM and you’ve probably noticed something: it doesn’t work. Why? It doesn’t work because you don’t have access to the dispatcher: you can only get to it from inside of classes inheriting UI controls (such as UserControl, Page, …). Quite logical, actually: since it’s used for trying to get back to the UI thread, it’s useless in non-UI-related code. Operations like that aren’t allowed: for example, try to access the Applications’ RootVisual from inside a VM, and you’ll get an unauthorized access exception.
However, in MVVM, we actually DO need access to the Dispatcher. Luckily, it’s not that hard to do. The solution I prefer is keeping the dispatcher in a static variable, so it’s accessible from anywhere in your application. To achieve this, create a class (eg: LocalStateContainer) with a static variable in your application. In your App Start, fill this variable with the dispatcher:
LocalStateContainer.Dispatcher = RootVisual.Dispatcher;
Now, you can access it from anywhere in your application to get back to the UI thread when needed.
To get back to our original problem, following code will solve it:
PersonServiceReference.PersonServiceClient client = new PersonServiceReference.PersonServiceClient(); client.LoadPersonsCompleted += (s, a) => { LocalStateContainer.Dispatcher.BeginInvoke(() => { if (a.Error == null) { AllPersons = new ObservableCollection<Person>(a.Result); } }); }; client.LoadPersonsAsync();
Hope this helps some of you out! 🙂