Click here to Skip to main content
15,879,348 members
Articles / Web Development / ASP.NET
Article

BackgroundWorker in Silverlight

Rate me:
Please Sign up or sign in to vote.
4.33/5 (4 votes)
4 Feb 2008CPOL2 min read 49.6K   330   38   2
Why not create our own BackgroundWorker for use with Silverlight?

Image 1

Introduction

In the current public alpha of Silverlight 1.1, there is no dispatcher like the wonderful BackgroundWorker for WinForms developers (since 2.0) and using the Thread in Silverlight is so not easy. For a “WinFormer”, this is a huge drawback. Without BackgroundWorker, you can only use or create controls in the Silverlight main thread (except if you like cross thread exception...). In WinForms, the logic is to manipulate controls in the main thread and do logic and mathematical computing in another “Logic” thread. Every specific period, a refresh is made to coordinate things. The BackgroundWorker is specifically devoted to this kind of task. Let's start making our own Silverilght BackgroundWorker.

First thing: A timer

The idea is to create a Storyboard by code with an animation duration of 100 milliseconds (you can change the value). Each time the Storyboard is completed, you have to analyse the associated thread currently running and generate events, if needed. I do not use an HtmlTimer because the class is deprecated.

C#
/// <summary>
/// <para>Instanciate a new <see cref="BackgroundWorker"/> object.</para>
/// </summary>
/// <param name="host">A control witch can host children.</param>
public BackgroundWorker(Panel host)
{
    if (host == null)
        throw new ArgumentNullException("host", "Host must be valid");
    this._host = host;
    this.ConfigureStoryboard();
}

The ConfigureStoryboard method creates the Timer:

C#
/// <summary>
/// <para>Configure the ticks timer storyboard.</para>
/// </summary>
private void ConfigureStoryboard()
{
    if (!BackgroundWorker._hostsToStoryboards.ContainsKey(this._host))
    {
        string xamlCode = string.Format(BackgroundWorker._tickTimerStoryboardXaml,
                          Guid.NewGuid().ToString());
        Storyboard storyboard = XamlReader.Load(xamlCode) as Storyboard;
        this._host.Resources.Add(storyboard);
        StoryboardData data = new StoryboardData() { Storyboard=storyboard, 
                              IsStarted = false, NumberOfHostManaged=0 };
        BackgroundWorker._hostsToStoryboards.Add(this._host, data);
    }
    bool isStarted = BackgroundWorker._hostsToStoryboards[this._host].IsStarted;
    BackgroundWorker._hostsToStoryboards[this._host].Storyboard.Duration = 
        new TimeSpan(0, 0, 0, 0, this._refreshInterval);
    if (isStarted)
        this.StartTicks();
}

The hostsToStoryboards field is a Dictionary witch associates a Host to a Storyboard. This helps to have only one timer run by the host (and not one by the BackgroundWorker). The tickTimerStoryboardXaml field contains a serialized XAML Storyboard:

C#
private static string _tickTimerStoryboardXaml = 
 @"<Storyboard xmlns:x=""http://schemas.microsoft.com/winfx" + 
 @"/2006/xaml""x:Name=""TickTimer_{0}""></Storyboard>"; 

The StoryboardData class informs if a Storyboard is started, and the number of threads currently listened to by the Storyboard. The RefreshInterval property is simply the Timer period:

C#
BackgroundWorker._hostsToStoryboards[this._host].Storyboard.Duration = 
                new TimeSpan(0, 0, 0, 0, this._refreshInterval);

Each time the Storyboard is completed, the Logical Thread is analysed:

C#
private void BackgroundTickOccurs(object sender, EventArgs e)
{
    if (this._progressChangedEventArgs != null)
    {
        if (this.ProgressChanged != null)
            this.ProgressChanged(this, this._progressChangedEventArgs);
        this._progressChangedEventArgs = null;
    }
    if (this._completed && !this._completedLaunched)
    {
        BackgroundWorker._hostsToStoryboards[this._host].NumberOfHostManaged--;
        
        if (this.RunWorkerCompleted != null)
            this.RunWorkerCompleted(this, this._runWorkerCompletedEventArgs);
        this._isBusy = false;
        this._completedLaunched = true;
    }
    if (BackgroundWorker._hostsToStoryboards[this._host].NumberOfHostManaged <= 0)
    {
        BackgroundWorker._hostsToStoryboards[this._host].Storyboard.Stop();
        BackgroundWorker._hostsToStoryboards[this._host].Storyboard.Seek(
                                              new TimeSpan(0, 0, 0, 0, 0));
    }
    BackgroundWorker._hostsToStoryboards[this._host].NumberOfHostManaged--;
    if (!this._completedLaunched)
        this.StartTicks();
}

If the Logic Thread has instantiated a ProgressChangedEventArgs object, I generate a ProgressChanged event. If the thread is completed and the Completed event is not launched, I generate a RunWorkerCompleted event. If the storyboard has no Thread to analyse, it is stopped.

The logic thread

The InternalJob method is the ThreadStart delegate of the thread. This method is called asynchronously from the main thread.

C#
/// <summary>
/// <para>Execute the internal job represented
///     by the linked DoWork method.</para>
/// </summary>
private void InternalJob()
{
    this._doWorkEventArgs = new DoWorkEventArgs(this._argument);
    this._runWorkerCompletedEventArgs = new RunWorkerCompletedEventArgs(null);
    try
    {
        if (this.DoWork != null)
            this.DoWork(this, this._doWorkEventArgs);
        this._runWorkerCompletedEventArgs.Result = this._doWorkEventArgs.Result;
    }
    catch (Exception e)
    {
        this._runWorkerCompletedEventArgs.Error = e;
    }
    this._completed = true;
}

The DoWork linked method is called inside a try-catch block. If the method completes well, the runWorkerCompletedEvent takes the return result of the doWorkEventArgs passed to the DoWork method. If an exception is caught, the runWorkerCompletedEvent takes the exception.

Calling the Worker

First instantiate the Worker, passing the host for the timer:

C#
this._worker = new BackgroundWorker(this.Host);

Then, link your event methods:

C#
this._worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
this._worker.ProgressChanged += 
     new ProgressChangedEventHandler(_worker_ProgressChanged);
...

And to finish, start the Worker:

C#
this._worker.RunWorkerAsync();

The sample

I made a simple sample, where a BackgroundWorker will animate an X shape in a canvas. When you click on a link, a shape is added on the client area.

Apologizes

The article may be difficult to read: I speak very bad English and I apologize for this. I hope this code will help you, like it helped me. Don’t hesitate to send me comments! (Yes, in English if you can :) ).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Viablue - GraphicStream
France France
I spent most of time on Silverlight, Xna (where i am MVP) and ADO.Net Data Services.

Comments and Discussions

 
QuestionDownload Sample - hidden extension Warning Pin
Erhy12-Feb-08 21:47
Erhy12-Feb-08 21:47 
GeneralRe: Download Sample - hidden extension Warning Pin
Valentin Billotte14-Feb-08 1:58
Valentin Billotte14-Feb-08 1:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.