简体   繁体   English

通过后台工作者进行枚举

[英]Enumerating through background workers

Is there a way to enumerate through all background workers? 有没有办法列举所有背景工作者? Currently, I've written a small method that I add to as I create new workers. 目前,我已经编写了一个小方法,可以在创建新工作程序时添加到其中。 Only one worker should run at a time, so the check method is: 一次只能运行一个工人,因此检查方法是:

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

At the time a button is clicked that would start a worker, the click method does this: 在单击将启动工作程序的按钮时,click方法将执行以下操作:

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

        else
        {
            bgWorkerFoo.RunWorkerAsync();
        }

I'd like to clean up my CheckForWorkers() method so that I don't need to add to it anytime a new worker is created for a different task, but I can't seem to find any way to run through each worker associated with the app. 我想清理我的CheckForWorkers()方法,以便在为不同任务创建新工作程序时无需添加它,但是我似乎找不到任何方法来运行与之相关的每个工作程序与应用程序。 Maybe there isn't a way? 也许没有办法? Do all of the workers exist (are instantiated) prior to being used? 所有工人在使用之前是否存在(实例化)?

Why not do something similar to this? 为什么不这样做呢?

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

It's likely you'll need to refer to the collection of workers in the future as well, so it makes sense to put them into a collection anyway 将来您可能还会需要引用工人的集合,因此无论如何都要将它们放入集合

Or, for non-C#6 syntax, a bit uglier: 或者,对于非C#6语法,则有点难看:

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

To dynamically get all fields/properties in your class, you can do this: 要动态获取类中的所有字段/属性,可以执行以下操作:

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

Might be a good idea to cache GetWorkers() , but it depends on your use-case. 缓存GetWorkers()可能是一个好主意,但这取决于您的用例。

I'm going to suggest an alternative approach that may be appealing. 我将建议一种可能有吸引力的替代方法。 The Microsoft Reactive Framework introduced aa lot of very useful functionality for dealing with concurrency and threads. Microsoft Reactive Framework引入了许多用于处理并发和线程的非常有用的功能。 Primarily the framework is used to deal with event sources in terms of IObservable<T> , but the framework also provides a lot of schedulers for dealing with processing on different threads. 最初,该框架用于根据IObservable<T>处理事件源,但是该框架还提供了许多调度程序,用于处理不同线程上的处理。

One of the schedulers is called EventLoopScheduler and this allows you to create a scheduler that runs on a background thread and only allows one operation to occur at any one time. 其中一个调度程序称为EventLoopScheduler ,它使您可以创建在后台线程上运行的调度程序,并且该调度程序只能在任一时间发生。 Any thread can schedule tasks and tasks can be scheduled immediately or in the future, or even recurringly. 任何线程都可以安排任务,并且可以立即或将来甚至是重复地安排任务。

The key point here is that you don't need to track multiple background workers as it doesn't matter how many operations you schedule they'll only run in series . 这里的关键是您不需要跟踪多个背景工人,因为您安排多少操作只能连续运行无关紧要。

When using Windows Forms you can use a scheduler called ControlScheduler that allows you to set up a scheduler that will post operations to the UI thread. 使用Windows窗体时,可以使用称为ControlScheduler的调度程序,该调度程序允许您设置将操作发布到UI线程的调度程序。

Once you have these two set up they become very easy to use. 一旦设置了这两个,它们就变得非常易于使用。

Try this code: 试试这个代码:

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

When I run this code I get this form showing on screen: 当我运行此代码时,将在屏幕上显示此表单:

形成

Clearly the calculation is correct and it can be seen that the thread ids are different. 显然,计算是正确的,可以看出线程ID是不同的。

You can even do more powerful things like this: 您甚至可以执行以下更强大的操作:

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

This code is creating a timer to run every second on the background thread. 这段代码创建了一个计时器,该计时器每秒钟在后台线程上运行一次。 It uses the state variable to keep track of the number of times that it ran and updates the UI with the value of state when it is even, and otherwise, schedules on its own scheduler some code that will schedule on the UI to update label1.Text with the value "odd" . 它使用state变量来跟踪其运行的次数,并在state为偶数时使用state值更新UI,否则,将在其自己的调度程序上调度一些将在UI上进行调度以更新label1.Text值为"odd" label1.Text It can get quite sophisticated, but everything is serialized and synchronized for you. 它可以变得非常复杂,但是所有内容都已为您序列化和同步。 Since this created a timer there is a mechanism to shut the timer down and that is calling booking.Dispose() . 由于这创建了一个计时器,因此有一种机制可以关闭计时器,这就是调用booking.Dispose()

Of course, since this is using the Reactive Framework you could just use standard observables to do the above, like this: 当然,由于这是使用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);

Notice that the same schedulers are used. 注意,使用了相同的调度程序。

If you need to perform the same task for a (potentially large or unlimited) number of variables, this likely means that they are "homogenious" in some manner and thus should probably be combined into some kind of a "registry" container instead. 如果需要对一定数量的变量(可能很大或无限)执行相同的任务,则可能意味着它们在某种程度上是“同质的”,因此可能应合并为某种“注册表”容器。

Eg I used to do something like this: 例如,我曾经做过这样的事情:

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

The specific implementation of the registry van be anything, depending on what you need to do with your objects (eg a dictionary if you need to get a specific one in other scope than the one it was created in). 注册表的具体实现可以是任何东西,具体取决于您需要对对象执行什么操作(例如,如果需要在不同于创建它的范围的其他范围内获得特定的字典,则可以是字典)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM