Click here to Skip to main content
Click here to Skip to main content

BackgroundWorker in Silverlight

, 4 Feb 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
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.

/// <span class="code-SummaryComment"><summary></span>
/// <span class="code-SummaryComment"><para>Instanciate a new <see cref="BackgroundWorker"/> object.</para></span>
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><param name="host">A control witch can host children.</param></span>
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:

/// <span class="code-SummaryComment"><summary></span>
/// <span class="code-SummaryComment"><para>Configure the ticks timer storyboard.</para></span>
/// <span class="code-SummaryComment"></summary></span>
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.

/// <span class="code-SummaryComment"><summary></span>
/// <span class="code-SummaryComment"><para>Execute the internal job represented</span>
///     by the linked DoWork method.<span class="code-SummaryComment"></para></span>
/// <span class="code-SummaryComment"></summary></span>
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 Smile | :) ).

License

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

Share

About the Author

Valentin Billotte
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 PinmemberErhy12-Feb-08 22:47 
Hello,
 
I wanted to download SilverlightProjectBackgroundWorkerSample but
my Virus Scanner warned me about hidden extension .js .
 
Please tell me about.
 
Erhy
GeneralRe: Download Sample - hidden extension Warning PinmemberMember 350102714-Feb-08 2:58 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141223.1 | Last Updated 5 Feb 2008
Article Copyright 2008 by Valentin Billotte
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid