多线程的背景工人设计

时间:2010-11-14 21:40:12

标签: c# wpf multithreading silverlight timer

在多线程编程或技术方面没有经验,我想将这个问题作为一种方法来集中设计,以实现对于相对长时间运行的作业(4到10秒)的实际启动一些用户采取行动的时间:

  1. 作业开始后直到完成:
      *显示一些不确定的进度指示器(即旋转球,进度条)
      *显示作为状态更新
    的一部分的已用秒数的运行计数
  2. 使用MVVM设计,其中IsBusy的状态和概念是某些INPC视图模型类中的数据绑定属性
  3. 单元可测试

我开始尝试使用子类或其他封装的BackgroundWorker的路径,但发现自己被绊倒了如何同步计算定时器线程上的经过秒数的概念,而另一个线程正在进行工作。

可能导致更集中的编程问题的设计理念非常受欢迎!

干杯,
Berryl

3 个答案:

答案 0 :(得分:2)

使用DispatcherTimer更新视图模型中的ElapsedTime属性。这是从GUI线程调用的,因此您可以直接绑定到该属性。使用BackgroundWorker执行线程任务。

public class ViewModel : INotifyPropertyChanged  
{
  public DateTime ElapsedTime {get; private set;}
  public bool IsRunning {get; private set;}

  private BackgroundWorker worker = new BackgroundWorker();
  private DateTime startTime;
  private DispatcherTimer t = new DispatcherTimer();

  public ViewModel()
  {
    t.Interval = 500; 
    t.Tick += (ox,ex) => UpdateTime();
    worker.DoWork += YourMethodHere;
    worker.RunWorkerCompleted += (ox,ex)=> {
      IsRunning = false;
      if (PropertyChanged != null)
       PropertyChanged(this, new PropertyChangedEventArgs("IsRunning"));
    };
  }

  public void UpdateTime()
  {
     ElapsedTime=startTime.Subtract(DateTime.Now);
     if (PropertyChanged != null)
       PropertyChanged(this, new PropertyChangedEventArgs("ElapsedTime"));
  }

  public void Start()
  {
    startTime=DateTime.Now;
    worker.RunWorkerAsync();
    IsRunning = true;
    if (PropertyChanged != null)
     PropertyChanged(this, new PropertyChangedEventArgs("IsRunning"));
  }

}

您可以将进度条GUI元素绑定到视图模型中的bool属性,该属性在启动和完成后台任务时更新(使用RunWorkerComplete事件)。

答案 1 :(得分:2)

BackgroundWorker类是开启后台任务的绝佳工具,但它的设计迫使开发人员走错路径,有时候应该如何更新UI。问题是它使用推模型来更新UI。换句话说,ReportProgressProgressChanged成员旨在将事件处理程序封送到UI线程上。这对许多情况都很好,但在大多数其他情况下它不能很好地工作......正如你所注意到的那样。

更新UI的另一种策略是让UI线程定期轮询共享数据结构以获取进度信息。工作线程将向此共享数据结构发布新的进度信息,UI线程将按照自己的计划进行提取。这有几个好处。

  • 它打破了Control.Invoke强加的UI和工作线程之间的紧密耦合。
  • 它将更新UI线程的责任放在它应该属于的UI线程上。
  • UI线程决定更新的发生时间和频率。
  • UI消息泵没有溢出的风险,就像工作线程启动的编组技术一样。
  • 在继续执行下一步之前,工作线程不必等待确认已执行更新(即,您在UI和工作线程上获得更多吞吐量)。
  • 实现MVVM IsBusy数据绑定属性要容易得多。

如果要切换到轮询方法,则必须废弃BackgroundWorker类并手动启动新线程(或使用ThreadPool)。

答案 2 :(得分:0)

我的工作线程使用回调方法来更新包含进度和消息的UI线程中的集合,并在UI线程中使用单独的计时器来更新UI,我的结果很好。在集合周围使用锁定来考虑并发性。回调函数还会向工作线程返回一个继续/停止标志,以实现正常的关闭控制。