Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

BackgroundWorker in Silverlight

0.00/5 (No votes)
4 Feb 2008 1  
Why not create our own BackgroundWorker for use with Silverlight?

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.

/// <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:

/// <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:

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:

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:

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.

/// <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:

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

Then, link your event methods:

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

And to finish, start the Worker:

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here