使用TPL和/或BackgroundWorker处理间歇性IO作业 [英] Processing intermittent IO jobs with TPL and/or BackgroundWorker

查看:65
本文介绍了使用TPL和/或BackgroundWorker处理间歇性IO作业的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个与一些串行和USB设备交互的C#应用​​程序.我试图找到一种以并行方式命令这些设备的好方法,以便可以一次控制许多设备.我将通过用户输入以及脚本命令向设备启动命令.

I have a C# application that interacts with a few serial and USB devices. I am trying to find a nice way to command these devices in a parallel fashion so that many can be controlled at once. I will initiate commands to the device via user input and also scripted commands.

我目前正在尝试寻找一种干净的方式来收集"并与UI线程并行运行命令.我有多种形式,每种设备一种,每种都有一些可以发出命令的控件.

I am currently trying to figure out a clean way to "collect" and run commands in parallel to my UI thread. I have multiple forms, one for each device, each have a few controls that can issue commands.

我当前看到它的方式是,用户将单击一个按钮,该按钮将触发表单中的事件.另一个类称为CommandManager,它将挂接到所有这些事件中.每个事件都会传递必要的信息,以形成发送到设备的命令.

The way I currently see it a user will click a button which will fire an event in the form. Another class lets call it CommandManager will hook into all of these events; each event passes the necessary information to form a command to send to a device.

当事件由CommandManager处理时,它将形成命令并将其添加到名为DeviceCommandWorker的子类BackgroundWorker中的BlockingCollection<Command>中,该子类在应用程序打开时启动.它所做的只是循环遍历包含Task.Factory.StartNew()调用的代码块.

When the event is handled by the CommandManager it forms the command and adds it to a BlockingCollection<Command> in a subclassed BackgroundWorker named DeviceCommandWorker which was started when the application opened. All it does is loop over a block of code containing a Task.Factory.StartNew() call.

StartNew块中,在BlockingCollection上调用Take,等待输入命令.一旦命令进入集合,Take将返回,而Task则以愉快的方式运行. BackgroundWorker循环返回并重复该过程,直到被取消.

In the StartNew block Take is called on the BlockingCollection waiting for a command to be input. Once a command is in the collection the Take returns and the Task goes off on its merry way. The BackgroundWorker loops back around and repeats the process until it is cancelled.

// Event handler running on the UI Thread
public void ProcessCommand(DeviceCommand command)
{
    // I assume I cannot add to Commands (BlockingCollection) from here?
    DeviceCommandWorker.Instance.Commands.Add(command);
}

// ....
// BackgroundWorker started upon Application startup
// ...

public class DeviceCommandWorker : BackgroundWorker
{
    public static DeviceCommandWorker Instance { get { return lazyInstance.Value; } }
    private static readonly Lazy<DeviceCommandWorker> lazyInstance = new Lazy<DeviceCommandWorker>(() => new DeviceCommandWorker());

    public BlockingCollection<DeviceCommand> Commands { get; set; } 

    private DeviceCommandWorker()
    {
        WorkerSupportsCancellation = true;
        Commands = new BlockingCollection<DeviceCommand>();
    }

    protected override void OnDoWork(DoWorkEventArgs e)
    {
        while (!CancellationPending)
        {
            var commandTask = Task.Factory.StartNew(() =>
            {
                // If the BackGroundWorker is cancelled, how to esacpe this blocking call?
                DeviceCommand command = commandQueue.Take(); 

                DeviceCommandResult result;
                command.Process(out result);

                if(result == DeviceCommandResult.Error)
                    ; // How would I inform the UI of this result?
            });
        }
        e.Cancel = true;
    }
}

我的问题已在上面的代码中陈述,但我会重申.

My questions are stated in the code above but I will reiterate them.

我的第一个问题是,我认为我不能从正在运行的BackGroundWorker外部添加到BlockingCollection吗? (通过阅读有关TPL的信息,添加时是否应该没有可以锁定的同步对象?)

My first issue is that I do not think I can add to the BlockingCollection from outside the running BackGroundWorker? (From reading about the TPL, shouldn't there be a sync object I can lock on when adding?)

假设我可以添加到集合中,当Take方法被阻塞时,是否没有办法逃避它,特别是如果BackgroundWorker被取消了,它不会永远被阻塞吗? (可能在取消之前,我可以发送一个自定义命令",该命令仅创建一个不执行任何操作的任务,然后我就可以退出while循环)

Assuming that I can add to the collection, is there no way to escape the Take method when it is blocking, specifically if the BackgroundWorker was cancelled wouldn't it just be blocked forever? (Possibly before cancellation I could send a "custom command" that simply creates a task that does nothing, I would then be able to exit the while loop)

最后,我如何将命令执行成功或错误报告给UI线程?我已经看到了 SO答案,这是前进的正确方向吗?

Finally, how would I report either success or errors in command execution back to the UI thread? I have seen this SO answer, is that the right direction to head in?

推荐答案

因此,在进行了一些修补之后,看来我最初对上述想法很满意.我不确定要进行一些实际工作时会如何运行,但到目前为止我很高兴.

So after some more tinkering it looks like I got the idea above to work pretty well for me initially. I am not sure how it will perform when it comes time to do some real work but I am happy so far.

所以我重新编写了代码.我发现,在Task.Factory.StartNew()之外没有Take的情况下,我实际上只是在尽可能快地完成每个等待从BlockingCollection消耗的任务.因此,我将该语句移出了循环.我还确认,要摆脱阻塞Take,我将需要发送某种特殊命令,以便可以停止后台工作程序.最后(未显示),我计划使用Control.Invoke将所有失败返回给UI线程.

So I reworked the code. I found that without the Take outside of the Task.Factory.StartNew() I was actually just making tasks as fast as possible each waiting to consume from the BlockingCollection. So I moved the statement outside of the loop. I also confirmed that to break from the blocking Take I will need to send some sort of special command so that I can stop the background worker. Finally (not shown) I plan to use Control.Invoke to return any failures to the UI thread.

public Boolean StartCommandWorker()
{
    if (DeviceCommandWorker.Instance.IsBusy)
        return false;
    else
    {
        Console.Out.WriteLine("Called start command worker!");
        DeviceCommandWorker.Instance.RunWorkerAsync();
        return DeviceCommandWorker.Instance.IsBusy;
    }
}

public void StopCommandWorker()
{
    Console.Out.WriteLine("Called stop command worker!");
    ProcessCommand("QUIT");
    DeviceCommandWorker.Instance.CancelAsync();
}

public Boolean ProcessCommand(String command)
{
    DeviceCommandWorker.Instance.Commands.Add(command);
    Console.Out.WriteLine("Enqueued command \"" + command + "\" ThreadID: " + Thread.CurrentThread.ManagedThreadId);

    return true;
}

internal class DeviceCommandWorker : BackgroundWorker
{
    public static DeviceCommandWorker Instance { get { return lazyInstance.Value; } }
    private static readonly Lazy<DeviceCommandWorker> lazyInstance = new Lazy<DeviceCommandWorker>(() => new DeviceCommandWorker());

    public BlockingCollection<String> Commands { get; set; }

    private DeviceCommandWorker()
    {
        WorkerSupportsCancellation = true;
        Commands = new BlockingCollection<String>();
    }

    protected override void OnDoWork(DoWorkEventArgs e)
    {
        Console.Out.WriteLine("Background Worker Started ThreadID: " + Thread.CurrentThread.ManagedThreadId);

        while (!CancellationPending)
        {
            Console.Out.WriteLine("Waiting for command on ThreadID: " + Thread.CurrentThread.ManagedThreadId);
            String aString = Commands.Take();

            var commandTask = Task.Factory.StartNew(() =>
            {
                Console.Out.WriteLine("   Dequeued command \"" + aString + "\" ThreadID: " + Thread.CurrentThread.ManagedThreadId);
                if (aString.Equals("QUIT"))
                    Console.Out.WriteLine("   Quit worker called: " + aString);
            });
        }

        Console.Out.WriteLine("Background Worker: Stopped!");
        e.Cancel = true;
    }
}

这是一些示例输出.我制作了一个小的UI表单,可以用来启动,停止和发送命令.它只是发送一个随机双精度值作为命令.

Here is some sample output. I made a small UI form that I could start, stop, and send commands with. It simply sends a random double as a command.

Called start command worker!
Background Worker Started ThreadID: 17
Waiting for command on ThreadID: 17
Enqueued command "Hello" ThreadID: 10
Waiting for command on ThreadID: 17
   Dequeued command "Hello" ThreadID: 16
Enqueued command "0.0745" ThreadID: 10
Waiting for command on ThreadID: 17
   Dequeued command "0.0745" ThreadID: 12
Enqueued command "0.7043" ThreadID: 10
   Dequeued command "0.7043" ThreadID: 16
Waiting for command on ThreadID: 17
Called stop command worker!
Enqueued command "QUIT" ThreadID: 10
   Dequeued command "QUIT" ThreadID: 12
   Quit worker called: QUIT
Background Worker: Stopped!
Enqueued command "0.2646" ThreadID: 10
Enqueued command "0.1619" ThreadID: 10
Enqueued command "0.5570" ThreadID: 10
Enqueued command "0.4129" ThreadID: 10
Called start command worker!
Background Worker Started ThreadID: 12
Waiting for command on ThreadID: 12
Waiting for command on ThreadID: 12
Waiting for command on ThreadID: 12
Waiting for command on ThreadID: 12
Waiting for command on ThreadID: 12
   Dequeued command "0.2646" ThreadID: 16
   Dequeued command "0.1619" ThreadID: 16
   Dequeued command "0.5570" ThreadID: 16
   Dequeued command "0.4129" ThreadID: 16
Enqueued command "0.8368" ThreadID: 10
   Dequeued command "0.8368" ThreadID: 17
Waiting for command on ThreadID: 12
Called stop command worker!
Enqueued command "QUIT" ThreadID: 10
   Dequeued command "QUIT" ThreadID: 16
   Quit worker called: QUIT
Background Worker: Stopped!

这篇关于使用TPL和/或BackgroundWorker处理间歇性IO作业的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆