Threading is a well-known feature to push time consuming tasks to a separate thread. Silverlight, by default, is running our code in the UI thread, which means that if we have a method that takes time to finish, the UI thread will be blocked and won't allow interaction until the method finishes running. By pushing this time consuming method to a different thread, the method will still take time to complete, but the UI thread won't be blocked, and the user can keep interacting with it while the method computes.
While Silverlight doesn't offer all of the threading options that the full .NET framework offers, it's still powerful enough to drive multithreaded applications. One important aspect you have to remember when working with threads is that your code runs in a whole other environment than your UI, and as such you cannot access any of the elements in the UI thread. Don't fear though; a workaround for this will be shown shortly.
The BackgroundWorker
class of Silverlight provides us an easy way to run time-consuming code on a background thread. Moving time consuming calculations to this background thread will free our UI thread from freezing, and give our users the sense of interactivity that they are expecting. The BackgroundWorker
class provides two important properties for interacting with it as follows:
WorkerSupportsCancellation:
A Boolean property indicating whether the background operation can be cancelled or notWorkerReportsProgress:
A Boolean property indicating whether the background operation should report its progress or notBut these properties aren't the reason for which we have gathered here today. The one most important aspect of the BackgroundWorker
class is, without a doubt, the DoWork
event handler.
This event handler is where you run your resource hog code on the background thread. If we wish to report progress from the DoWork
event handler to the calling process, we call the ReportProgress
method, passing it a completion percentage from 0 to 100. Take note though, if we set the WorkerReportProgress
property of the BackgroundWorker
class to false
, the ReportProgress
method will throw an exception. To pass data back to the calling process (which is basically the whole point of this event handler), we set the Result
property of the DoWorkEventArgs
object, which is passed to the event handler. We can read this value when the RunWorkerCompleted
event is raised, at the end of the operation. Let's get a first-hand impression on how to use this awesome feature!
To get started, fire up Visual Studio 2010, and load the Chapter4-BackgroundWorker project from the training files. The project right now is nothing more than a header and a vertical StackPanel
consisting of a TextBlock
element and a ProgressBar
element. We will use the ProgressBar
element to show the progress of our background worker in a nice graphical way. Perform the following steps:
MainPage.xaml.cs
file and add the following using command:using System.ComponentModel;
private
variable:private BackgroundWorker _bw;
This will be our BackgroundWorker
object. We will initiate it under the Loaded
event handler.
MainPage
constructor, add the following line of code to create an event handler for the Loaded
event:this.Loaded += new RoutedEventHandler(MainPage_Loaded);
void MainPage_Loaded(object sender, RoutedEventArgs e) { _bw = new BackgroundWorker(); _bw.WorkerReportsProgress = true; _bw.DoWork += new DoWorkEventHandler(_bw_DoWork); _bw.ProgressChanged += new ProgressChangedEventHandler(_bw_ProgressChanged); _bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_bw_RunWorkerCompleted); _bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_bw_RunWorkerCompleted); _bw.RunWorkerAsync(); }
The preceding code snippet initiates the BackgroundWorker
object and sets its WorkerReportProgress
property to true
. By setting this property to true, we enable the background worker to report the progress of its task.
DoWork:
This event handler gets called once we call the RunWorkerAsync
method of BackgroundWorker
.ProgressChanged:
This event handler gets called whenever we call the ReportProgress
method of BackgroundWorker
.RunWorkerCompleted:
This event handler gets called once the background operation is completed RunWorkerAsync
method to get the background operation started.Let's add the event handler methods now, starting with the DoWork
handler.
MainPage.xaml.cs:
void _bw_DoWork(object sender, DoWorkEventArgs e) { for (int i = 1; i < 11; i++) { System.Threading.Thread.Sleep(1000); _bw.ReportProgress(i * 10); } }
What we have here is a simple for
loop, going from 1 to 10, which will suspend the currently used thread for one second at a time. Once it wakes up, the method will call the ReportProgress
method of the BackgroundWorker
object passing it the percentage of job completion.
The two remaining event handlers—ProgressChanged
and RunWorkerCompleted
—update the UI with the progress of the background operation.
MainPage.xaml.cs
file:void _bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { lblBar.Text = "Loading completed!"; prgsBar.IsIndeterminate = true; } void _bw_ProgressChanged(object sender, ProgressChangedEventArgs e) { prgsBar.Value = e.ProgressPercentage; }
Nothing particularly interesting is happening in these two methods. The RunWorkerCompleted
method updates the TextBlock
and ProgressBar
elements to indicate that the operation is completed. The ProgressChanged
method makes use of the ProgressPercentage
property of ProgressChangedEventArgs
to set the ProgressBar
element's value. Remember, we have passed the ProgressChanged
event handler the value to update using the ReportProgress
method earlier.
Everything is done! Build and run your application, and you should get the following screenshot:
The progress bar will keep filling up, reporting the progress made in the background operation until it gets to 100 percent.
And with this project running, you've finished creating your first BackgroundWorker
based application.
When working with different threads, there may come a time when you have to deal with cross threading. Imagine a case, where your background thread needs to update a control, which is positioned on the UI thread. If you try to update it directly, you'll be greeted with the "Invalid cross-thread access" exception. This happens because a thread in Silverlight cannot access data on another thread directly. So how do you update the UI thread from another thread? You use the Dispatcher
object.
The Dispatcher
object exposes the BeginInvoke
method, which allows us to access the UI thread very easily. Consider the following line of code:
Dispatcher.BeginInvoke(UpdateUI);
The preceding line of code will call the UpdateUI
method on the UI thread itself, and not the background thread it was called from.
If you want, you can use lambda expressions to make this code event shorter:
Dispatcher.BeginInvoke(() => { prgsBar.Value = 70; });
The Dispatcher
object is a very important concept of Silverlight threading, because without it, we wouldn't be able to interact with the UI thread from any background thread at all. This is the key object that is in charge of the cross threading between the background and UI threads.
The DispatcherTimer
object represents, well, a timer. The difference between the DispatcherTimer
object and any other timer class you might have encountered before is that the DispatcherTimer
object is integrated into the Dispatcher
queue. This means that the timer is processed at a specified interval of time and at a specified priority. The DispatcherTimer
object exposes the Interval
property, which accepts a TimeSpan
object and lets you decide the interval at which the timer will execute. Consider the following code snippet:
DispatcherTimer timer = new DispatcherTimer(); timer.Interval = new TimeSpan(0, 0, 20); timer.Tick += new EventHandler(timer_Tick); timer.Start();
The preceding code snippet initiates a DispatcherTimer
object called timer
, and then sets its interval to 20
seconds. The Tick
handler defines the handler that gets called once the interval has elapsed. To get the timer started, you have to call the Start
method of the object. If you wish to stop the timer at any point, you just call the Stop
method. Create a new Silverlight 4 project in Visual Studio and copy the preceding code snippet to your MainPage.xaml.cs
file. If you put a breakpoint at the beginning of the timer_Tick
method, you would notice it breaks at that point every 20 seconds. Try to play around with the TimeSpan
object, and the Start
and Stop
methods of the DispatcherTimer
object to get a better understanding of this mechanism.
3.144.41.229