65.9K
CodeProject 正在变化。 阅读更多。
Home

Silverlight 中的 BackgroundWorker

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (4投票s)

2008年2月4日

CPOL

2分钟阅读

viewsIcon

50690

downloadIcon

347

为什么不为 Silverlight 创建我们自己的 BackgroundWorker?

引言

在当前的Silverlight 1.1公开Alpha版本中,没有像WinForms开发人员(自2.0版以来)使用的优秀的BackgroundWorker这样的调度程序,并且在Silverlight中使用Thread非常不容易。对于“WinFormer”来说,这是一个巨大的缺点。如果没有BackgroundWorker,你只能在Silverlight主线程中使用或创建控件(除非你喜欢跨线程异常……)。在WinForms中,逻辑是在主线程中操作控件,并在另一个“逻辑”线程中进行逻辑和数学计算。每隔一段时间,就会进行一次刷新以协调操作。BackgroundWorker专门用于此类任务。让我们开始创建我们自己的Silverlight BackgroundWorker

首先:一个计时器

这个想法是通过代码创建一个持续时间为100毫秒的Storyboard(你可以更改此值)。每次Storyboard完成后,你必须分析当前运行的关联线程,并在需要时生成事件。我不使用HtmlTimer,因为此类已弃用。

/// <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();
}

ConfigureStoryboard方法创建计时器

/// <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();
}

hostsToStoryboards字段是一个Dictionary,它将Host与Storyboard关联起来。这有助于仅让一个计时器由Host运行(而不是由BackgroundWorker运行)。tickTimerStoryboardXaml字段包含一个序列化的XAML Storyboard。

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

StoryboardData类告知是否启动了Storyboard,以及Storyboard当前侦听的线程数。RefreshInterval属性只是Timer周期。

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

每次Storyboard完成后,都会分析逻辑线程。

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();
}

如果逻辑线程实例化了一个ProgressChangedEventArgs对象,我将生成一个ProgressChanged事件。如果线程已完成并且未启动Completed事件,我将生成一个RunWorkerCompleted事件。如果Storyboard没有要分析的线程,则将其停止。

逻辑线程

InternalJob方法是线程的ThreadStart委托。此方法从主线程异步调用。

/// <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;
}

DoWork链接方法在try-catch块内调用。如果方法顺利完成,则runWorkerCompletedEvent将获取传递给DoWork方法的doWorkEventArgs的返回值。如果捕获到异常,则runWorkerCompletedEvent将获取该异常。

调用Worker

首先实例化Worker,并为计时器传递Host。

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

然后,链接你的事件方法。

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

最后,启动Worker。

this._worker.RunWorkerAsync();

示例

我做了一个简单的示例,其中BackgroundWorker将在画布上动画显示一个X形状。当你点击链接时,将在客户端区域添加一个形状。

致歉

这篇文章可能难以阅读:我的英语非常糟糕,对此我深感抱歉。我希望这段代码能帮助你,就像它帮助我一样。请随时向我发送评论!(如果你可以的话,请用英语)。

© . All rights reserved.