Updating the UI from Asynchronous Ops

Friday, May 15, 2009 / Posted by Luke Puplett /

A Technique for RIA UI Control Updates and MVVM

Its a fairly widely known fact of Windows programming that you can't update a UI control from the code executing in your painstakingly well-written worker object. There are a number of Framework constructs that are designed to help in these situations such as the Dispatcher as well as more low-level classes like the AsyncOperationManager which puts a slightly more friendly veneer over the SynchronisationContext. Whichever class you use, updating the UI usually involves writing dedicated methods in some place away from where the action is taking place.

I'm going to talk about using one of the simplest of the lot to re-unite things: BackgroundWorker.

You'll be able to go from writing asynchronous code straight to writing values to your view model with just a few lines in-between where the magic happens - all within the same method - so you can even pipe the variables declared and available within your concurrent method directly into the UI. Sounds to good to be true? Well I hope not because it seems to be working here.

About our Weapon of Choice

First up, a little bit of background about background, err worker. BackgroundWorker’s design centres on three events, DoWork, RunWorkerCompleted and ProgressChanged (I’d have called the first one RunWork but there you go).

Essentially, DoWork is triggered on a new thread-pool thread while the others are triggered on the UI thread.

Be careful though because ConsoleApplications seem not to jump back to Main Thread and I’m not totally sure if the design aim was to trigger on the UI thread specifically or just the same thread that the RunWorkerAsync method was called from.

I'd suggest reading more about it here (VB.NET examples).

The Delegate and its Wrapper

Jumping straight into code, the first thing needed is a vanilla delegate; parameterless and void. Next is a class which holds a delegate - we can't pass delegates in the way I'd like because they don't derive from Object so I’m sort of wrapping it up in a class which can be passed around.

delegate void UIWorkDelegate();

/// <summary>
/// Wraps a delegate in a class so the class can be passed to methods
/// only accepting type Object.
/// </summary>
class UIWorkWrapper
{
    public UIWorkWrapper(UIWorkDelegate work)
    {
        this.UIWork = work;
    }

    public UIWorkDelegate UIWork { get; set; }
}
Setting-up and Calling BackgroundWorker

To kick off the concurrent logic, we need to add some code somewhere in a method on the UI thread, either in a Window or some helper class. I’ve kept some extraneous code around to give it some context.

public partial class Window1 : Window 

    public Window1() 
    { 
        try 
        { 
            InitializeComponent(); 
        } 
        catch (Exception e) 
        { 
            System.Diagnostics.Debugger.Break(); 
        } 
    } 

    void StartSyncManager() 
    { 
        BackgroundWorker syncWorker = new BackgroundWorker(); 
        syncWorker.WorkerReportsProgress = true
        syncWorker.WorkerSupportsCancellation = true

        syncWorker.DoWork += new DoWorkEventHandler(syncWorker_DoWork); 
        syncWorker.ProgressChanged += new ProgressChangedEventHandler(syncWorker_ProgressChanged); 

        VolatileState.Synchroniser = new SyncManager(); 

        syncWorker.RunWorkerAsync(); 
    } 

    void syncWorker_DoWork(object sender, DoWorkEventArgs e) 
    { 
        VolatileState.Synchroniser.StartSynchLoop(syncWorker, new ViewModel(this)); 
    } 

    void syncWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) 
    { 
        var w = (UIWorkWrapper)e.UserState; 
        w.UIWork.Invoke(); 
    } 

    private void Start_Click(object sender, RoutedEventArgs e) 
    { 
        if (VolatileState.Synchroniser == null
        { 
            this.StartSyncManager(); 
            ((Button)sender).Content = "Stop"
        } 
        else 
        { 
            VolatileState.Synchroniser.Stop();
            VolatileState.Synchroniser = null;  
            ((Button)sender).Content = "Restart"
        } 
    } 
}

The VolatileState object is a hang-over from my test code so ignore it and pretend its says 'this' and points to a property on the Window1 class or something.

Interesting thing number 1: The DoWork method calls the StartSyncLoop method on the SyncManager passing in the BackgroundWorker that’s hosting it and a ViewModel which in turn has a constructor which points to the page we’re on. If we’re relying on data binding to do everything then we don’t need to pass a reference to our window, we just need a ViewModel instance with all the right bindings. My example uses an explicit UI control manipulation because its more obvious.

Interesting thing number 2: The ProgressChanged event handler method casts the UserState property of the event args back to a UIWorkWrapper and then invokes the delegate its wrapping!

The Synchroniser Thingy Class

If you haven't guessed by now, to get into the spirit of asynchronous activity I’m using a hypothetical example of a synchronisation loop which might keep an application in synch with a web service or something. The premise being that this method is called by the DoWork event handler code and so will begin its duties on another thread (note that thread-pool threads are actually supposed to be used for short bursts of work and not long-running background synch maintenance type tasks... but I won’t tell anyone if you won’t).

public void StartSynchLoop(BackgroundWorker worker, ViewModel viewModel)
{            
    while (!_worker.CancellationPending)
    {
        int t1 = Thread.CurrentThread.ManagedThreadId; 
        worker.ReportProgress(
            0,
            new UIWorkWrapper(
                delegate
                {
                    viewModel.Status = "Synchronizing...";
                    viewModel.PostStatusMessage(String.Format("Outside tid {0}, this code tid {1}.", t1, Thread.CurrentThread.ManagedThreadId));
                })
            );
        
        ... // synch code.

And that’s it. The asynchronously running loop can modify the ViewModel directly via an anonymous method delegate which will be run on the UI thread - note how the variables flow right through. The Status property could be data-bound and the PostStatusMessage could do something like this (this.Control having been set in the constructor to the Window1 instance):

public void PostStatusMessage(string message)
{
    ((Window1)this.Control).MainListBox.Items.Add(message);            
}

Thanks and good day to you.

Labels: , ,

2 comments:

Comment by Luke Puplett on Thursday, June 04, 2009

Josh Smith of, I beleive Infragistics, wrote a great introduction and sample for the MVVM pattern and data-binding/templating. You can download it here:

http://code.msdn.microsoft.com/mag200902MVVM/Release/ProjectReleases.aspx?ReleaseId=2026

And read about it here:

http://msdn.microsoft.com/en-us/magazine/dd419663.aspx

Comment by Luke Puplett on Wednesday, June 24, 2009

UIWorkDelegate, right at the top of the code, should be changed to the more commonly understood System.Action vanilla method delegate that the Framework provides.

Post a Comment