A reader of my Silverlight 5 book recently reached out to
me about threading and why I create some objects on the UI thread
in the examples. We discussed some of the reasons, but I felt this
would be a good topic to share with everyone. In fact, this is one
area where it would have been fun to go into great detail in my
book, but there simply wasn't the space. Threading and cross-thread
exceptions can be a bit of a mystery to new Silverlight and WPF
developers.
Background
The user interface in Silverlight runs on a thread commonly
known as the UI Thread. Any code you create in the code-behind, and
any code it calls all the way down the chain, unless it explicitly
creates another thread, runs on this same UI thread. It's not at
all uncommon to see Silverlight and WPF applications which never
explicitly create a second thread, but do make calls to other
services which create background threads for processing.
There are many other examples, but networking is one place where
the Silverlight .NET Framework explicitly creates (or uses)
different threads. Not all calls return on the UI thread.
Threads other than the UI thread are not allowed to
access or manipulate UI objects. If they attempt to do so,
the runtime throws an Invalid Cross-Thread Access exception. It
looks like this:
But wait! I wasn't accessing any UI objects from my code. What
gives?
It's not always obvious that you're interacting with UI objects
on the UI thread, though. Here's the stack trace from this
particular exception:
{System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. --->
System.UnauthorizedAccessException: Invalid cross-thread access.
at MS.Internal.XcpImports.CheckThread()
at MS.Internal.XcpImports.GetValue(IManagedPeerBase managedPeer, DependencyProperty property)
at System.Windows.DependencyObject.GetOldValue(DependencyProperty property, EffectiveValueEntry& oldEntry)
at System.Windows.DependencyObject.UpdateEffectiveValue(DependencyProperty property, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, ValueOperation operation)
at System.Windows.DependencyObject.RefreshExpression(DependencyProperty dp)
at System.Windows.Data.BindingExpression.SendDataToTarget()
at System.Windows.Data.BindingExpression.SourcePropertyChanged(PropertyPathListener sender, PropertyPathChangedEventArgs args)
at System.Windows.PropertyPathListener.ReconnectPath()
at System.Windows.Data.Debugging.BindingBreakPoint.<>c__DisplayClass4.<BreakOnSharedType>b__3()
--- End of inner exception stack trace ---
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
at System.Delegate.DynamicInvokeImpl(Object[] args)
at System.Delegate.DynamicInvoke(Object[] args)
at MainPagexaml.BindingOperation(Object BindingState, Int32 , Action )}
This stack trace was generated by trying to raise a
PropertyChangedNotification when I manipulated a model object from
a background thread. So, it was obvious that I was working
with an object on the background thread, but it wasn't obvious that
I'd get a cross-thread exception (well it was in this case, as I
contrived the example). If you consider a larger application where
you have division of ownership for different pieces, a client-side
developer may simply work with your viewmodel, but not realize
you're farming some work out to another thread.
I wrote a blog post back in 2010 (
Essential Silverlight and WPF Skills: The UI Thread, Dispatchers,
Background Workers and Async Network Programming) explaining
some of the ways to work with threads and dispatching. I didn't
have SynchronizationContext in there at the time, but it's
something I tend to use a lot these days.
Let's take a look at a few of the common scenarios and how it
works with threading.
Common Scenarios
All of these scenarios make use of a simple Customer class with
a single property:
namespace SilverlightThreadingExample.Model
{
public class Customer : Observable
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set { _firstName = value; NotifyPropertyChanged("FirstName"); }
}
}
}
The customer is observable that is, it notifies any
listeners when properties change. While not necessary, I
encapsulated the observable code in this base class. You could,
instead, put the implementation in a partial class if you wanted to
make sure your model object's signature from your ORM or whatever
remains the same as it was on the server.
using System.ComponentModel;
namespace SilverlightThreadingExample
{
public class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
This is actually a pretty typical approach to handling
INotifyPropertyChanged. There are even more robust versions out
there which use lambdas and reflection to help avoid passing in
strings, but they ultimately come down to raising the
PropertyChanged event. Many of them also fail to work properly in
cross-thread situations.
I expose the Customer class and a collection of customers from a
ViewModel class.
namespace SilverlightThreadingExample.ViewModel
{
public class CustomerEntryViewModel : Observable
{
private Customer _currentCustomer;
public Customer CurrentCustomer
{
get { return _currentCustomer; }
set { _currentCustomer = value; NotifyPropertyChanged("CurrentCustomer"); }
}
private ObservableCollection<Customer> _customers = new ObservableCollection<Customer>();
public ObservableCollection<Customer> Customers
{
get { return _customers; }
}
public void LoadCustomersOnSameThread()
{
LoadDummyData();
}
private void LoadDummyData()
{
_customers.Add(new Customer() { FirstName = "Pete" });
_customers.Add(new Customer() { FirstName = "Jon" });
_customers.Add(new Customer() { FirstName = "Tim" });
_customers.Add(new Customer() { FirstName = "Scott" });
_customers.Add(new Customer() { FirstName = "Andy" });
_customers.Add(new Customer() { FirstName = "Blaine" });
_customers.Add(new Customer() { FirstName = "Jesse" });
_customers.Add(new Customer() { FirstName = "Rey" });
_currentCustomer = _customers[0];
}
}
}
Finally, the UI is bound to those classes. The DataContext for
the UI (which will be set in code-behind) is the ViewModel.
<UserControl x:Class="SilverlightThreadingExample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="600">
<Grid x:Name="LayoutRoot" Background="White">
<Grid Width="500">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="250" />
</Grid.ColumnDefinitions>
<ListBox x:Name="CustomerList"
Grid.Column="0" Margin="10"
ItemsSource="{Binding Customers}"
SelectedItem="{Binding CurrentCustomer, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FirstName}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1">
<TextBox x:Name="FirstNameField" Margin="10"
DataContext="{Binding CurrentCustomer}"
Text="{Binding FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button x:Name="AddCustomer"
Content="Add Customer from UI Thread"
Height="30" Margin="5"
Click="AddCustomer_Click" />
<Button x:Name="AddCustomerSecond"
Content="Add Customer from Second Thread"
Height="30" Margin="5"
Click="AddCustomerSecond_Click" />
<Button x:Name="ChangeNameFromSecondThread"
Content="Change Name from Second Thread"
Height="30" Margin="5"
Click="ChangeNameFromSecondThread_Click" />
</StackPanel>
</Grid>
</Grid>
</UserControl>
The code-behind looks like this
using System.Windows;
using System.Windows.Controls;
using SilverlightThreadingExample.ViewModel;
using System.Threading;
using SilverlightThreadingExample.Model;
namespace SilverlightThreadingExample
{
public partial class MainPage : UserControl
{
CustomerEntryViewModel _vm;
public MainPage()
{
InitializeComponent();
CreateViewModelOnUIThread();
}
private void CreateViewModelOnUIThread()
{
_vm = new CustomerEntryViewModel();
_vm.LoadCustomersOnSameThread();
DataContext = _vm;
}
private void AddCustomer_Click(object sender, RoutedEventArgs e)
{
}
private void AddCustomerSecond_Click(object sender, RoutedEventArgs e)
{
}
private void ChangeNameFromSecondThread_Click(object sender, RoutedEventArgs e)
{
}
}
}
The methods are named to keep the context clear in this example.
I wouldn't expect you to name your VM instantiation/locator method
"CreateViewModelOnUIThread", for example.
Now let's look at those scenarios.
Changing a property value from a background thread
Often in an application, you have a class which is used in UI
binding (an entity/model object) but still need to modify a
property from code. Sometimes, you need to do that from a
background thread. For example, you make a network call to get an
updated price for an item. You will get a cross-thread access error
when the class raises change notification events from that property
setter. If the class is truly POCO (Plan Old CLR Object) and
doesn't do any change notification or other event raising, you'll
be fine. If, however, change notification is involved, you'll get
the cross-thread exception.
Assuming the same Customer and Observable classes defined above,
and the same XAML UI, the following code in the code-behind will
throw that exception.
private void ChangeNameFromSecondThread_Click(object sender, RoutedEventArgs e)
{
Thread t = new Thread((o) =>
{
_vm.CurrentCustomer.FirstName = "UpdatedFromSecondThread";
});
t.Start();
}
The exception doesn't happen when you set the property value;
that's perfectly acceptable. It happens here, at the highlighted
line:
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
So, what are the options for working around this? Let's consider
two common approaches.
Approach 1
The first approach is to do the whole property change from the
UI thread. To do this, simply wrap the property set code with a
call to the dispatcher. You can use either the Dispatcher object,
or if you keep a copy of the SynchronizationContext around, use
that.
Here's some example code which implements this. The code looks a
bit silly because I'm forcing a background thread. However, pretend
that the background thread is a given and you need to work from it
(again, the callback from a network call or something.)
private void ChangeNameFromSecondThread_Click(object sender, RoutedEventArgs e)
{
Thread t = new Thread((o) =>
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
_vm.CurrentCustomer.FirstName = "UpdatedFromSecondThread";
});
});
t.Start();
}
This approach works well, but requires the calling code to
handle all the dispatching. If you have a number of properties to
change in different bits of code, it gets a bit cumbersome. Let's
look at another approach.
Approach 2
The real problem is the property change notification, so the the
second approach is to dispatch just the change notification to the
UI thread.
You can put this code into the Observable base class in order to
avoid repeating it throughout all your classes.
// Code-behind
// ----------------------------------------------
// this version throws an exception if the Observable base class isn't doing thread checking
private void ChangeNameFromSecondThread_Click(object sender, RoutedEventArgs e)
{
Thread t = new Thread((o) =>
{
_vm.CurrentCustomer.FirstName = "UpdatedFromSecondThread";
});
t.Start();
}
// Observable
// ----------------------------------------------
using System.ComponentModel;
using System.Windows;
namespace SilverlightThreadingExample
{
public class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
if (Deployment.Current.Dispatcher.CheckAccess())
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
else
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
});
}
}
}
}
}
Note how I first check to see if we have access to the UI thread
using the CheckAccess call. If we do, there's no reason to incur
the overhead and delay of a dispatcher call. However, if we are
running on the background thread, then the call is dispatched to
the UI thread. In either case, we avoid the errors cause by
cross-thread property change notification.
Next up: Collections
Populating an ObservableCollection from a networking return
call
A more robust Observable base class like this won't help with
collection change notification (WPF 4.5 has a great solution for
that using BindingOperations.EnableCollectionSynchronization, but
unfortunately Silverlight does not).
Most applications make networking calls to get information from
some resource on an intranet or out on the web. In Silverlight (and
WPF), it's common practice to populate an ObservableCollection with
the results from those calls. The ObservableCollection class is
nice because it implements INotifyCollectionChanged and raises an
event whenever items are added to or deleted from the collection,
or when the collection is cleared. It's this notification that
enables the various items controls and grids in the UI to stay in
sync with the items in the collection.
This version is very similar to what we saw with individual
properties earlier. That's because, it's really the same problem:
we're trying to notify the binding system across threads.
private void AddCustomerSecond_Click(object sender, RoutedEventArgs e)
{
Thread t = new Thread((o) =>
{
var cust = new Customer() { FirstName = "AddedFromSecondThread" };
_vm.Customers.Add(cust);
});
t.Start();
}
Note that the problem exists regardless of where you actually
create the customer. For example, this code will also fail:
private void AddCustomerSecond_Click(object sender, RoutedEventArgs e)
{
var cust = new Customer() { FirstName = "AddedFromSecondThread" };
Thread t = new Thread((o) =>
{
_vm.Customers.Add(cust);
});
t.Start();
}
The reason is, again, it's not the object access that is causing
the cross-thread exception, it's the collection changed
notification that's full of hate here.
So, how do you get around this? Unless you want to create your
own ObservableCollection type class for Silverlight, you'll need to
dispatch all collection add calls. Luckily, you'll typically have
fewer of these scattered throughout the application, so it's not
quite so onerous.
The code to implement this is just another easy call to the
dispatcher (or SynchronizationContext, if you prefer).
private void AddCustomerSecond_Click(object sender, RoutedEventArgs e)
{
// you can create customer on any thread
var cust = new Customer() { FirstName = "AddedFromSecondThread" };
Thread t = new Thread((o) =>
{
// dispatch to UI thread to add it to the collection. You can't
// access the observable collection x-thread
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
_vm.Customers.Add(cust);
});
});
t.Start();
}
If you're going to run from an unknown state, be sure to call
CheckAccess to see if you really need to do the dispatching. In
this case, I know I'm always going to be on a background thread, so
I don't bother.
Summary
The intent here was to show a few of the common threading
pitfalls in Silverlight (and WPF) applications, specifically in the
context of change notification. In most code, it's easy to tell
when you're accessing objects cross-thread, but change notification
is a somewhat behind the scenes operation, so it's not always
obvious.
For property change notification, the solutions were:
- Dispatch the entire property change operation to the UI
thread
- Update the NotifyPropertyChanged code to check to see which
thread it's running on, and then dispatch the event as
appropriate
Either way works, but I prefer the update to the
NotifyPropertyChanged method.
For collection change notifications in Silverlight, the
solutions are:
- Create (or find) an implementation of ObservableCollection
which does the cross-thread checking. The reason this isn't
built-in is change notification happens often, and dispatching each
and every change notification can be a real performance drain.
That's also why WPF has a separate and optimized solution. You'd
need to enable batching to avoid the overhead of hundreds of
dispatch calls.
- Dispatch the entire collection update when you're running on a
background thread.
In contrast to the property change notifications, for collection
change, I prefer to dispatch the entire call. If you're adding 1
object or 100, you'll still get only one dispatch call, so
performance is better.
The Task Parallel Library in WPF, and the subset of it in
Silverlight also offer some alternative approaches to handling
cross-thread work. Similarly, the async and await keywords in .NET
4.5 and Windows 8 XAML can also come into play. More on those in
the future.