等待所有排队的背景工作者完成?

时间:2015-05-28 13:54:29

标签: c# wpf multithreading .net-4.0

我正在尝试一些QueuedBackgroundWorker课,我发现here。它运作良好,除了我想知道我怎么能等到所有排队的工人都完成了?例如,如果用户关闭程序,我希望程序等到所有工人完成然后关闭。

我尝试在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
}

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:0)

如果你这样做

while (myWorkerQueue.Queue.Count > 0) ;

你的while循环占用了大量的资源,以至于你的后台线程已经没有了。它似乎被阻止了。

如果你想保持你的while循环(我不建议),至少要睡一觉,这样你的后台线程就可以工作了:

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

您在评论中说的最简单的解决方案是挂钩结束事件并在myWorkerQueue.Queue.Count&gt;中止。 0

更优雅的解决方案是使用进度条创建模态表单,在表单关闭时显示它,如果myWorkerQueue.Queue.Count&gt; 0,进度条将随着剩余的后台工作人员完成...