代码之家  ›  专栏  ›  技术社区  ›  Berryl

多线程后台工作程序设计

  •  1
  • Berryl  · 技术社区  · 15 年前

    由于没有多线程编程或技术方面的经验,我想问这个问题,作为一种集中设计的方式,以实现由某些用户操作实时启动的相对长时间运行的作业(4到10秒)的以下要求:

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

    我开始尝试使用一个BackgroundWorker,这个BackgroundWorker可以是子类的,也可以是封装的,但是我发现自己被一个错误绊倒了:当另一个线程正在工作时,如何同步计时器线程上的已用秒数。

    设计思想,可能导致更集中的编程问题非常感谢!

    干杯,
    柏瑞尔

    3 回复  |  直到 15 年前
        1
  •  2
  •   Guy    15 年前

    使用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事件)。

        2
  •  2
  •   Brian Gideon    15 年前

    这个 BackgroundWorker 类是启动后台任务的一个很好的工具,但它的设计有时会迫使开发人员走上错误的道路,有时会考虑如何更新UI。问题是它使用push模型来更新UI。换句话说 ReportProgress ProgressChanged 成员旨在将事件处理程序封送到UI线程上。在很多情况下都可以,但是 其他情况下,它不太好…你已经注意到。

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

    • 它打破了UI和工作线程之间的紧密耦合 Control.Invoke 强加。
    • 它把更新UI线程的责任放在UI线程上,不管它应该属于哪里。
    • UI线程可以指定更新的时间和频率。
    • 不存在UI消息泵被溢出的风险,就像由工作线程启动的封送处理技术一样。
    • 在继续下一步之前,工作线程不必等待更新已执行的确认(即,您在UI和工作线程上都获得了更多的吞吐量)。
    • 实现MVVM要容易得多 IsBusy 数据绑定属性。

    你将不得不放弃 后台工作人员 初始化并手动启动新线程(或使用 ThreadPool )如果要切换到轮询方法。

        3
  •  0
  •   Ed Power    15 年前

    我的工作线程使用回调方法来更新包含进度和消息的UI线程中的集合,并在UI线程中使用单独的计时器来更新UI,取得了很好的效果。使用集合周围的锁定来注意并发性。回调还向工作线程返回一个continue/stop标志,以实现正常的关闭控制。