Updating DataBound Controls
asynchronously: AsyncBindingList<T>
Daniel
Herling
Microsoft
Corporation
April 2006
Summary:
This
sample shows how to extend BindingList<T> so that it can be updated on a
background thread.
Applies
To:
Complex
bound controls ( DataGridView, ListBox and ComboBox ) and simple bound
controls.
Download the sample
Introduction:
A common
task Client applications are very good at is presenting business data. The said
business data can be stored in a configuration file, a SQL Server, or it can be
made available on a web service. For instance, the applications title may be a
string stored in SQL Server data table.
When the data
changes, the UI changes as well so it reflects the new data. Re-using the
previous example, if the string stored in the SQL Server data table changes
then the applications title needs to change as well.
However,
Windows Forms controls can only be updated on the UI thread. So, if SQL Server
changes the string on a different thread than the UI thread updating the
applications title on the same thread would be a problem. Luckily, Windows
Forms controls have two methods which can be called from any thread in order to
update the controls: BeginInvoke and Invoke. I wont get into the details for
BeginInvoke and Invoke since they are well documented and understood. I will
only give an example on how to use BeginInvoke:
// Compute the new title on a background thread...
string newTitle = "new title";
this.BeginInvoke((MethodInvoker) delegate {
// ... and set the application's text on the UI thread
this.Text = newTitle;
});
Note that
after computing the new title the background thread calls Control.BeginInvoke
to set the applications text and after that the background thread is freed to
do other processing. The
example above was a pretty straightforward example. Life gets more interesting
when we add data binding to the application. Data
binding is a mechanism that displays data from a data source into the client
application. Also, data binding is used to update the data source from changes
done through the applications UI. If data
changes in a background thread that poses a problem to application developers
because they need to essentially call BeginInvoke for each of this data change:
see the following links.
http://lab.msdn.microsoft.com/ProductFeedback/viewFeedback.aspx?feedbackid=ea5add7d-76e2-4e88-bbc9-2f348187be23
http://lab.msdn.microsoft.com/ProductFeedback/viewFeedback.aspx?feedbackid=a75683d8-746a-4b70-9bdd-94b7c7b647d5
The
solution is to data bind the UI controls via an AsyncBindingList<T>. This
will solve the problem of making changes to the data source on threads other
than the UI thread.
AsyncBindingList<T>:
implementation details
The
philosophy behind the AsyncBindingList<T> is to get the updates from the
background thread and forward them to the UI thread using
WindowsFormsSynchronizationContext.
An update
is one of the following operations: Add/AddNew/Insert/Delete/Clear/SetItem. For
each such operation the AsyncBindingList<T> constructs a DataTransaction
which it then sends to the WindowsFormsSynchronizationContext. The
WindowsFormsSynchronizationContext then posts the DataTransaction to the UI
thread. All the work is done on the UI thread and the background thread is free
to immediately do other work.
With two
exceptions, the background thread is free to do other work right away after
updating the AsyncBindingList<T>; the background thread does not need to
wait for the UI to update. The two
methods that need to be executed synchronously are:
- AsyncBindingList<T>::get_Count.
We need this to execute synchronously because otherwise we cant reliably
use the Count property. In other words, if get_Count property does not
execute synchronously then your app cant write the following logic
// remove the last item in the list
list.RemoveAt(list.Count - 1);
- AsyncBindignList<T>::AddNew().
Since AddNew() has a return value, if your app needs the value of AddNew()
then AddNew() needs to be executed synchronously. If your app does not
need the return value from AddNew(), then AddNew() does not need to
execute synchronously.
AsyncBindingList<T>:
how to use
There are
only two rules to using AsyncBindingList<T>:
- instantiate
the AsyncBindingList<T> on the UI thread
- update
the AsyncBindingList<T> ( via one of its
Add/AddNew/Insert/Delete/Clear methods ) on the background thread.
This
posting contains a sample app that does exactly that. The app updates the
background thread when a System.Timers.Timer fires the elapsed event. Since the
AsyncBindingList<T> is not multi threaded ( see Further improvements
section) , the updates to AsyncBindingList are serialized in the sample.
Further
improvements:
A
limitation with the current implementation is that the AsyncBindingList<T>
is not thread safe. The sample application attached updates the
AsyncBindingList<T> only from one thread. The current implementation
could be made thread safe by using a lock on its overridden methods.