2015-05-28 50 views
0

我正在尝试一些QueuedBackgroundWorker类,我发现here。它运作良好,除了我想知道如何等待所有排队的工人完成工作?例如,如果用户关闭程序,我希望程序等待所有工人完成并关闭。等待所有排队的BackgroundWorker完成?

我尝试了GUI线程做这样的事情,但它只是似乎要禁止:

 try 
     { 
      while (myWorkerQueue.Queue.Count > 0) ; 

     } 
     catch (InvalidOperationException) 
     { 

     } 

也试过while(myWorkerQueue.Queue.Peek() != null),得到了相同的结果。

代码QueuedBackgroundWorker

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Web; 
using System.ComponentModel; 

/// <summary> 
/// This is thread-safe 
/// </summary> 
public class QueuedBackgroundWorker 
{ 
    #region Constructors 

     public QueuedBackgroundWorker() { } 

    #endregion 

    #region Properties 

    Queue<object> Queue = new Queue<object>(); 

    object lockingObject1 = new object(); 

    public delegate void WorkerCompletedDelegate<K>(K result, Exception error); 

    #endregion 

    #region Methods 

    /// <summary> 
    /// doWork is a method with one argument 
    /// </summary> 
    /// <typeparam name="T">is the type of the input parameter</typeparam> 
    /// <typeparam name="K">is the type of the output result</typeparam> 
    /// <param name="inputArgument"></param> 
    /// <param name="doWork"></param> 
    /// <param name="workerCompleted"></param> 
    public void RunAsync<T,K>(Func<T, K> doWork, T inputArgument, WorkerCompletedDelegate<K> workerCompleted) 
    { 
     BackgroundWorker bw = GetBackgroundWorker<T,K>(doWork, workerCompleted); 

     Queue.Enqueue(new QueueItem(bw, inputArgument)); 

     lock (lockingObject1) 
     { 
      if (Queue.Count == 1) 
      { 
       ((QueueItem)this.Queue.Peek()).RunWorkerAsync();  
      } 
     } 
    } 

    /// <summary> 
    /// Use this method if you don't need to handle when the worker is completed 
    /// </summary> 
    /// <param name="doWork"></param> 
    /// <param name="inputArgument"></param> 
    public void RunAsync<T,K>(Func<T, K> doWork, T inputArgument) 
    { 
     RunAsync(doWork, inputArgument, null); 
    } 

    private BackgroundWorker GetBackgroundWorker<T, K>(Func<T, K> doWork, WorkerCompletedDelegate<K> workerCompleted) 
    { 
     BackgroundWorker bw = new BackgroundWorker(); 
     bw.WorkerReportsProgress = false; 
     bw.WorkerSupportsCancellation = false; 

     bw.DoWork += (sender, args) => 
     { 
      if (doWork != null) 
      { 
       args.Result = (K)doWork((T)args.Argument); 
      } 
     }; 

     bw.RunWorkerCompleted += (sender, args) => 
     { 
      if (workerCompleted != null) 
      { 
       workerCompleted((K)args.Result, args.Error); 
      } 
      Queue.Dequeue(); 
      lock (lockingObject1) 
      { 
       if (Queue.Count > 0) 
       { 
        ((QueueItem)this.Queue.Peek()).RunWorkerAsync();     
       } 
      } 
     }; 
     return bw; 
    } 

    #endregion 
} 

public class QueueItem 
{ 
    #region Constructors 

    public QueueItem(BackgroundWorker backgroundWorker, object argument) 
    { 
     this.BackgroundWorker = backgroundWorker; 
     this.Argument = argument; 
    } 

    #endregion 

    #region Properties 

    public object Argument { get; private set; } 

    public BackgroundWorker BackgroundWorker { get; private set; } 

    #endregion 

    #region Methods 

    public void RunWorkerAsync() 
    { 
     this.BackgroundWorker.RunWorkerAsync(this.Argument); 
    } 

    #endregion 
} 
+0

把UI线程进入一个“while”循环就像你看到前面是一个真正的糟糕的主意,因为现在UI线程不能再做任何UI工作(简单来说)。你的后台线程多久了?他们中的任何人使用Invoke或BeginInvoke来进行UI更新。更好的解决方案将取决于你的后台线程正在做什么。 – Christoph

+0

他们正在做一些背景计算和数据库读取/结果保存。每个工人通常不到几分钟。 – user17753

+0

“等待”是什么意思?如果你运行while(myWorkerQueue.Queue.Count> 0);在UI线程上,它阻塞,那么你正在等待 –

回答

4

你绝对必须使用BackgroundWorker吗? .NET 4中引入了Task API(又名任务并行库或TPL简称):你可以启动多个任务,并使用Task.WhenAll提供的延续,只有当所有任务完成后执行:

public partial class MainWindow : Window 
{ 
    public MainWindow() 
    { 
     InitializeComponent(); 
    } 

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e) 
    { 
     var myTasks = new List<Task<BitmapImage>>(); 
     // Task<T>.Factory.StartNew starts the given method on a Thread Pool thread 
     myTasks.Add(Task<BitmapImage>.Factory.StartNew(LoadPicture1)); 
     myTasks.Add(Task<BitmapImage>.Factory.StartNew(LoadPicture2)); 

     // The important part: Task.WhenAll waits asynchronously until all tasks 
     // in the collection finished sucessfully. Only then, the lambda that is 
     // given to the ContinueWith method is executed. The UI thread does not block 
     // in this case. 
     Task.WhenAll(myTasks) 
      .ContinueWith(task => 
          { 
           foreach (var bitmapImage in task.Result) 
           { 
            var image = new Image { Source = bitmapImage }; 
            ImageStackPanel.Children.Add(image); 
           } 
          }, 
          TaskScheduler.FromCurrentSynchronizationContext()); 
    } 

    private BitmapImage LoadPicture1() 
    { 
     return LoadImageFile("Picture1.jpg"); 
    } 

    private BitmapImage LoadPicture2() 
    { 
     // Simulate that this loading process takes a little bit longer 
     Thread.Sleep(1000); 
     return LoadImageFile("Picture2.jpg"); 
    } 

    private BitmapImage LoadImageFile(string path) 
    { 
     using (var fileStream = new FileStream(path, FileMode.Open)) 
     { 
      var bitmapImage = new BitmapImage(); 
      bitmapImage.BeginInit(); 
      bitmapImage.CacheOption = BitmapCacheOption.OnLoad; 
      bitmapImage.StreamSource = fileStream; 
      bitmapImage.EndInit(); 
      bitmapImage.Freeze(); 
      return bitmapImage; 
     } 
    } 
} 

你甚至可以使用异步等待,如果你对.NET 4.5进行编程(但我怀疑你使用这个版本的.NET,因为你在你的问题中提供了.NET 4.0标记)。无论如何,如果你想坚持几个BackgroundWorker对象,我会将它们全部封装在一个类中,并注册到它们的Completed事件。如果他们都提出这个事件,那么我会提出另一个事件,告诉他们所有的事情都已经完成。

您可以了解更多有关TPL这里:https://msdn.microsoft.com/en-us/library/dd537609(v=vs.110).aspx

您也可以下载整个的例子,我在这里创造:https://dl.dropboxusercontent.com/u/14810011/LoadSeveralItemsWithTasks.zip

+0

我只是使用队列设置,因为我希望每个任务都能连续执行。 LoadPicture2只会在LoadPicture1完成后才会启动。在这个例子中,我假设可能LoadPicture2在LoadPicture1之前完成,或者相反。 – user17753

+0

这是正确的。只需在任务上使用ContinueWith来定义此任务完成后应该发生的情况。还请查看我答案中的倒数第二个链接,以了解Task类可能实现的功能(很多)。 – feO2x

0

如果你做这样的事情

while (myWorkerQueue.Queue.Count > 0) ; 

while循环正在采取这么多的ressource没有更多的后台线程。它似乎被阻止。

如果你想保持你的while循环(我不建议),至少把睡眠中让你的后台线程可以工作:

while (myWorkerQueue.Queue.Count > 0) 
    System.Threading.Thread.Sleep(1000); 

,你在你的评论说,最简单的解决方案如果myWorkerQueue.Queue.Count> 0,则关闭关闭事件并中止它。

更优雅的解决方案是创建一个带进度条的模态窗体,当窗体关闭时显示它,以及myWorkerQueue.Queue。 Count> 0时,进度条会随着剩余的后台工作人员完成而进度...