Today I had a nice learning experience on how to handle “please wait” screens and asynchronous calls on Silverlight 2.
Say that you want to present a message to your user while he waits an asynchronous call to be completed. However, given the nature of the call, you can’t control how long it will take to complete, and thus that message may be just a quick blink in the screen. But what if you still want that message to be readable, even when the call answers almost instantaneously?
Using System.Threading.Thread.Sleep() is not a good option, since it will hold the current thread – that is, the UI thread. And the user will be probably pissed off for having his browser frozen by the application.
The solution: BackgroundWorker and Dispatcher classes.
System.ComponentModel.BackgroundWorker (MSDN link) is a class that allows instructions to be run on a dedicated thread, with support to cancellation and progress reporting, while Dispatcher (MSDN link) is a handy property from the UserControl class (the one used as a base class to Silverlight pages) that gives us access to the UI thread.
Let’s see a simple example on how to implement the proposed solution.
using System;
using System.ComponentModel;
using System.Threading;
public class Page : UserControl
{
// Bogus is any given class with an asynchronous call
private Bogus bogus;
private BackgroundWorker bgWorker;
public Page()
{
InitializeComponent();
// Creates the BackgroundWorker and says it supports cancellation
bgWorker = new BackgroundWorker();
bgWorker.WorkerSupportsCancellation = true;
// Registers the listener
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
// Instantiate the class that runs the asynchronous call
bogus = new Bogus();
}
#region Button handlers
private void btnCallAsync_OnClicked(object sender, EventArgs e)
{
// Registers the listener for handling the result for the asynchronous call
bogus.BogusCompleted += new BogusEventHandler(bogus_BogusCompleted);
// The "please wait" message
lblText.Text = "Please wait while operation is processed...";
// Run the BackgroundWorker thread
bgWorker.RunWorkerAsync();
}
// Handles cancelation of the asynchronous call
private void btnCancel_OnClicked(object sender, EventArgs e)
{
// Let's unregister the listener so that the asynchronous call
// result is not handled
bogus.BogusCompleted -= new BogusEventHandler(bogus_BogusCompleted);
// Removes the "please wait" message
lblText.Text = string.Empty;
// Cancels the BackgroundWorker thread
bgWorker.CancelAsync();
}
#endregion Button handlers
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Let's run it unless the BackgroundWorker is pending
// cancelation
if(!bgWorker.CancellationPending)
{
// CurrentThread is not UI thread, since we are inside
// BackgroundWorker thread
Thread.CurrentThread.Sleep(new TimeSpan(0, 0, 3));
// After sleeping for some time to allow users to read
// the message, we start the asynchronous call
bogus.RunBogusAsync();
}
}
private void bogus_BogusCompleted(sender object, BogusEventArgs e)
{
// Any operations you want to execute in the UI thread
// as a result of the asynchronous call
Dispatcher.BeginInvoke(() => { UIOperations(); });
// Unregister the listener since the call was already processed
// and it is registered back on btnCallAsync_OnClicked()
bogus.BogusCompleted -= new BogusEventHandler(bogus_BogusCompleted);
}
}
So now let’s see in detail what the above code does.
The xaml file includes three controls: lblText (TextBlock control), btnCallAsync and btnCancel (Button controls).
As shown on btnCallAsync_OnClicked() (lines 27 to 37), lblText will display the wait message when btnCallAsync is clicked. Additionally, it will register the listener for BogusCompleted event and trigger the execution of the background worker.
bgWorker_DoWork() (lines 54 to 68) will then sleep the BackgroundWorker thread for a while and finally call the asynchronous method RunBogusAsync() which we were originally interested in.
On BogusCompleted, bogus_BogusCompleted() (lines 70 to 79) is finally run and Dispatcher.BeginInvoke() is called (using lambda expressions) to run something on the UI thread. We also unregister the listener for BogusCompleted events, since it is registered back every time btnCallAsync is clicked.
Sure we have a problem in this approach: we sleep the thread for some seconds before calling the asynchronous call, and that might cause a long wait in case Bogus.RunBogusAsync() takes too long to execute. For example, if the asynchronous call took five seconds to complete, we would keep the user waiting for eight seconds.
As an exercise, put the BackgroundWorker thread to sleep after the call to Bogus.RunBogusAsync(), and only if the asynchronous call didn’t take more than three seconds, so that the total wait would be at least three seconds, but no unnecessary extra time when.



