等待所有排队的后台工作人员完成?
Waiting for all Queued Backgroundworkers to finish?
我正在尝试一些 QueuedBackgroundWorker
class 我发现 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
}
如果你这样做了
while (myWorkerQueue.Queue.Count > 0) ;
您的 while 循环占用了太多资源,以至于您的后台线程没有更多资源了。它似乎被阻止了。
如果你想保留你的 while 循环(我不建议这样做),至少让你的后台线程可以工作:
while (myWorkerQueue.Queue.Count > 0)
System.Threading.Thread.Sleep(1000);
正如您在评论中所说,最简单的解决方案是挂钩关闭事件并在 myWorkerQueue.Queue.Count > 0 时中止它。
一个更优雅的解决方案是创建一个带有进度条的模态表单,在表单关闭时显示它,如果 myWorkerQueue.Queue.Count > 0,进度条将在剩余的后台工作人员完成时继续前进。 .
您一定要使用 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 进行编程,您甚至可以使用 async await(但我怀疑您是否使用此版本的 .NET,因为您在问题中提供了 .NET 4.0 标记)。无论如何,如果您想坚持使用多个 BackgroundWorker 对象,我会将它们全部封装在一个 class 中并注册到它们的 Completed 事件。如果他们都提出了这个事件,那么我会提出另一个事件,告诉他们都已经完成。
您可以在此处了解有关 TPL 的更多信息:https://msdn.microsoft.com/en-us/library/dd537609(v=vs.110).aspx
您可以在此处下载我创建的整个示例:https://dl.dropboxusercontent.com/u/14810011/LoadSeveralItemsWithTasks.zip
我正在尝试一些 QueuedBackgroundWorker
class 我发现 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
}
如果你这样做了
while (myWorkerQueue.Queue.Count > 0) ;
您的 while 循环占用了太多资源,以至于您的后台线程没有更多资源了。它似乎被阻止了。
如果你想保留你的 while 循环(我不建议这样做),至少让你的后台线程可以工作:
while (myWorkerQueue.Queue.Count > 0)
System.Threading.Thread.Sleep(1000);
正如您在评论中所说,最简单的解决方案是挂钩关闭事件并在 myWorkerQueue.Queue.Count > 0 时中止它。
一个更优雅的解决方案是创建一个带有进度条的模态表单,在表单关闭时显示它,如果 myWorkerQueue.Queue.Count > 0,进度条将在剩余的后台工作人员完成时继续前进。 .
您一定要使用 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 进行编程,您甚至可以使用 async await(但我怀疑您是否使用此版本的 .NET,因为您在问题中提供了 .NET 4.0 标记)。无论如何,如果您想坚持使用多个 BackgroundWorker 对象,我会将它们全部封装在一个 class 中并注册到它们的 Completed 事件。如果他们都提出了这个事件,那么我会提出另一个事件,告诉他们都已经完成。
您可以在此处了解有关 TPL 的更多信息:https://msdn.microsoft.com/en-us/library/dd537609(v=vs.110).aspx
您可以在此处下载我创建的整个示例:https://dl.dropboxusercontent.com/u/14810011/LoadSeveralItemsWithTasks.zip