有没有办法列举所有后台工作人员?目前,我已经编写了一个小方法,我在创建新工作时添加了这个方法。一次只能运行一个工作人员,因此检查方法是:
private bool CheckForWorkers() // returns true if any background workers are currently running
{
bool ret = false;
if (bgWorkerFoo.IsBusy || bgWorkerMeh.IsBusy || bgWorkerHmpf.IsBusy || bgWorkerWorkyWorky.IsBusy || bgWorkerOMGStahp.IsBusy)
{
ret = true;
}
return ret;
}
单击按钮启动工作人员时,click方法执行此操作:
if (CheckForWorkers())
{
MessageBox.Show("File generation already in progress. Please wait.", "Message");
return;
}
else
{
bgWorkerFoo.RunWorkerAsync();
}
我想清理我的CheckForWorkers()方法,以便在为不同的任务创建新工作人员的任何时候我都不需要添加它,但我似乎无法找到任何方式来运行与应用程序关联的每个工作程序。也许没有办法?在使用之前是否存在(实例化)所有工人?
答案 0 :(得分:1)
为什么不做类似的事情?
Worker[] Workers => new[] { bgWorkerFoo, bgWorkerMeh, bgWorkerHmpf, bgWorkerWorkyWorky, bgWorkerOMGStahp };
private bool CheckForWorkers()
{
return Workers.Any(w => w != null && w.IsBusy);
}
您可能也需要在将来引用工作人员的集合,因此无论如何将它们放入集合中是有意义的
或者,对于非C#6语法,有点丑陋:
private Worker[] Workers { get { return new[] { bgWorkerFoo, bgWorkerMeh, bgWorkerHmpf, bgWorkerWorkyWorky, bgWorkerOMGStahp }; } }
private bool CheckForWorkers()
{
return Workers.Any(w => w != null && w.IsBusy);
}
要动态获取班级中的所有字段/属性,您可以执行以下操作:
private IEnumerable<Worker> GetWorkers()
{
var flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
var fields = GetType().GetFields(flags);
var fieldValues = fields.Select(f => f.GetValue(this)).OfType<Worker>();
var properties = GetType().GetProperties(flags);
var propertyValues = properties.Select(f => f.GetValue(this)).OfType<Worker>();
return fieldValues.Concat(propertyValues).Where(w => w != null);
}
private bool CheckForWorkers()
{
return GetWorkers().Any(w => w.IsBusy);
}
最好缓存GetWorkers()
,但这取决于你的用例。
答案 1 :(得分:1)
我将建议一种可能具有吸引力的替代方法。 Microsoft Reactive Framework为处理并发和线程引入了许多非常有用的功能。主要是框架用于处理IObservable<T>
方面的事件源,但框架还提供了许多调度程序来处理不同线程上的处理。
其中一个调度程序称为EventLoopScheduler
,这允许您创建在后台线程上运行的调度程序,并且只允许一次执行一个操作。任何线程都可以安排任务和任务,可以立即或将来安排,甚至可以重复安排。
这里的关键点是,您不需要跟踪多个后台工作人员,因为您安排的操作次数并不重要,只会 串联运行。
使用Windows窗体时,您可以使用名为ControlScheduler
的调度程序,该调度程序允许您设置将操作发布到UI线程的调度程序。
一旦你完成了这两个设置,它们就变得非常容易使用了。
试试这段代码:
var form1 = new Form();
var label1 = new Label();
label1.AutoSize = true;
form1.Controls.Add(label1);
form1.Show();
var background = new EventLoopScheduler();
var ui = new ControlScheduler(form1);
var someValue = -1;
background.Schedule(() =>
{
var z = 42 * someValue;
var bgTid = System.Threading.Thread.CurrentThread.ManagedThreadId;
ui.Schedule(() =>
{
var uiTid = System.Threading.Thread.CurrentThread.ManagedThreadId;
label1.Text = $"{z} calc on {bgTid} updated on {uiTid}";
});
});
当我运行此代码时,我会在屏幕上显示此表单:
显然计算是正确的,可以看出线程ID是不同的。
你甚至可以做更强大的事情:
var booking =
background.SchedulePeriodic(0, TimeSpan.FromSeconds(1.0), state =>
{
var copy = state;
if (copy % 2 == 0)
{
ui.Schedule(() => label1.Text = copy.ToString());
}
else
{
background.Schedule(() => ui.Schedule(() => label1.Text = "odd"));
}
return ++state;
});
form1.FormClosing += (s, e) => booking.Dispose();
此代码创建一个计时器,以便在后台线程上每秒运行一次。它使用state
变量来跟踪它运行的次数,并在偶数时使用值state
更新UI,否则,在其自己的调度程序上调度一些代码,在UI上安排使用值label1.Text
更新"odd"
。它可以变得非常复杂,但所有内容都是为您序列化和同步的。由于这创建了一个计时器,因此有一种机制可以关闭计时器并调用booking.Dispose()
。
当然,由于这是使用Reactive Framework,您可以使用标准的observable来执行上述操作,如下所示:
var query =
from n in Observable.Interval(TimeSpan.FromSeconds(1.0), background)
select n % 2 == 0 ? n.ToString() : "odd";
var booking = query.ObserveOn(ui).Subscribe(x => label1.Text = x);
请注意,使用了相同的调度程序。
答案 2 :(得分:0)
如果你需要为(可能很大或无限制)数量的变量执行相同的任务,这可能意味着它们在某种程度上是“同质的”,因此应该可以合并到某种“注册表”容器中代替。
E.g。我以前做过这样的事情:
var thread = new BgWorker();
pool.Add(thread);
<...>
foreach (var thread in pool) {
do_something();
}
注册表的具体实现可以是任何东西,具体取决于您需要对对象执行的操作(例如,如果您需要在其他范围内获取特定的字典而不是创建它的字典)。