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)
{
// This is not UI thread, since we are inside the
// BackgroundWorker execution thread
Thread.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.



#1 by bob on November 20, 2011 - 1:56 AM
Quote
Thread.CurrentThread.Sleep(new TimeSpan(0, 0, 3));
should be written as
Thread.Sleep(new TimeSpan(0, 0, 3));
since Sleep is a static method
#2 by Swaroop on January 19, 2012 - 9:54 PM
Quote
Good article !
I did exactly the same thing recently.
I made the background thread to sleep for 15sec after calling the async operation.
My problem is that, sometimes my async operation is completing in 5 sec while sometimes it goes over 15 sec.
If the operation gets over before 15 sec , its fine for me….i have no issues even if the user has to wait for sometime before the next operation begins.
But, if the operation takes more than 15 sec to complete, then my next operation is getting triggered by my first operation run_completed event. And I am displaying messages to the useron UI thread before the start of each operation and after the end of each operation.
What this does is, before my first operation message completed message, the second operation start message is getting displayed. And I cannot stop this.
The only way out for me right now is to increase my sleep time (from 15 – 30 sec) in order to get right messaging displayed to the user. Still I think this is just a workaround and not a proper solution.
What else can be done in this case? Do you have any suggestions for me to overcome this?
Thanks,
Swaroop
#3 by Rafael Costa on January 23, 2012 - 5:55 AM
Quote
Nice catch, bob! I wrote that from the top of my had and missed that detail. Fixed