通过后台工作者枚举

Enumerating through background workers

有没有办法枚举所有后台worker?目前,我已经编写了一个小方法,在创建新工作人员时添加到该方法中。一次只有一个worker应该运行,所以检查方法是:

    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;
    }

单击将启动工作程序的按钮时,单击方法会执行以下操作:

        if (CheckForWorkers())
        {
            MessageBox.Show("File generation already in progress.  Please wait.", "Message");
            return;
        }

        else
        {
            bgWorkerFoo.RunWorkerAsync();
        }

我想清理我的 CheckForWorkers() 方法,这样我就不需要在为不同任务创建新工作人员时添加它,但我似乎找不到任何方法来运行 通过与该应用关联的每个工作人员。也许没有办法?是否所有的工作人员在使用之前都存在(实例化)?

为什么不做类似的事情呢?

Worker[] Workers => new[] { bgWorkerFoo, bgWorkerMeh, bgWorkerHmpf, bgWorkerWorkyWorky, bgWorkerOMGStahp };
private bool CheckForWorkers() 
{
    return Workers.Any(w => w != null && w.IsBusy);
}

将来您可能还需要引用 worker 的集合,因此无论如何将它们放入集合中是有意义的

或者,对于 non-C#6 语法,有点丑陋:

private Worker[] Workers { get { return new[] { bgWorkerFoo, bgWorkerMeh, bgWorkerHmpf, bgWorkerWorkyWorky, bgWorkerOMGStahp }; } }
private bool CheckForWorkers() 
{
    return Workers.Any(w => w != null && w.IsBusy);
}

要动态获取 class 中的所有 fields/properties,您可以这样做:

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() 可能是个好主意,但这取决于您的 use-case。

如果您需要对(可能很大或无限的)数量的变量执行相同的任务,这可能意味着它们在某种程度上是 "homogenious",因此可能应该组合成某种类型的变量"registry" 容器。

例如我曾经做过这样的事情:

var thread = new BgWorker();
pool.Add(thread);
<...>
foreach (var thread in pool) {
    do_something();
}

registry van 的具体实现可以是任何东西,具体取决于您需要对对象执行的操作(例如,如果您需要在其他范围内获取特定对象而不是创建它的对象,则为字典)。

我将建议一种可能很有吸引力的替代方法。 Microsoft Reactive Framework 引入了许多非常有用的功能来处理并发和线程。框架主要是用来处理事件源的IObservable<T>,但是框架也提供了很多调度器来处理不同线程的处理。

其中一个调度程序称为 EventLoopScheduler,这允许您创建一个 运行 在后台线程上运行的调度程序,并且在任何时候只允许一个操作发生。任何线程都可以调度任务,任务可以立即调度,也可以在未来调度,甚至可以重复调度。

这里的关键点是你不需要跟踪多个后台工作人员,因为无论你安排多少操作他们都会系列中只有 运行.

使用 Windows 表单时,您可以使用一个名为 ControlScheduler 的调度程序,它允许您设置一个调度程序,该调度程序将 post 操作 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 上调度以使用值 "odd" 更新 label1.Text。它可能会变得相当复杂,但所有内容都已为您序列化和同步。由于这创建了一个计时器,因此有一种机制可以关闭计时器,即调用 booking.Dispose().

当然,因为这是使用 Reactive Framework,所以您可以使用标准的可观察对象来执行上述操作,如下所示:

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);

请注意,使用了相同的调度程序。