问题描述
目前我们的应用程序通过串行端口连接到 Arduino.我们发送一些 ASCII 格式的命令,并得到相同的回报.为此,我们有一个命令队列,一个专用于将这些命令写入端口的线程,以及一个专用于读取和处理所有传入回复的线程.类本身负责发送回复,这给了它太多责任(应该只负责端口操作,而不是业务逻辑).
Currently our application connects to an Arduino over a serial port. We send some ASCII-formatted commands, and get the same in return. To do this, we have a queue of commands, a thread dedicated to writing those commands to the port, and a thread dedicated to reading and handling all incoming replies. The class itself is responsible for dispatching the replies, which is giving it way too much responsibility (should just be responsible for port operations, not business logic).
我们宁愿以异步方式执行此操作.系统中的任何东西都可以发送带有回调函数和超时的命令.如果串口得到正确回复,则调用回调函数.否则,它会超时并可能调用第二个回调(或者可能是带有 succeeded?
标志的单个回调).
We would rather do this in an async manner. Anything in the system can send a command with a callback function and a timeout. If the serial port gets a correct reply, it calls the callback function. Otherwise, it times out and maybe calls a second callback (or possibly a single callback with a succeeded?
flag).
但是,我们只使用过异步方法(尤其是在 Web 操作中),没有编写过这样的系统.谁能给我们一些关于如何进行的指示?
However, we've only ever consumed async methods (particularly in web operations), not written such a system. Can anyone give us some pointers about how to proceed?
我们当前的计划是存储这些命令的队列.在任何回复后,如果找到关联的命令(通过比较 ASCII 值),则将其出列并执行回调.计时器将定期检查超时、出列并执行适当的回调.这似乎是一个简单的解决方案,但支持它的代码量正在大幅增加,我们希望确保没有更好的内置解决方案或最佳实践.
Our current plan is to store a queue of these commands. Upon any reply, if an associated command is found (by comparing ASCII values) it is dequeued and the callback is executed. A timer will periodically check for timeouts, dequeue, and execute the appropriate callback. It seems like a straightforward solution, but the amount of code to support this is increasing substantially and we wanted to ensure there weren't any better built-in solutions or best practices for this.
编辑:为了进一步澄清,这个特定的类是一个单例(无论好坏),并且有许多其他线程正在运行可以访问它.例如,一个线程可能想要请求传感器值,而另一个线程可能正在控制电机.这些命令及其相关回复不会以线性方式发生;时间可能会颠倒.因此,传统的生产者-消费者模型是不够的;这更像是一个调度程序.
Edit: To clarify further, this particular class is a singleton (for better or worse), and there are many other threads running that could access it. For example, one thread may want to request a sensor value, while another thread could be controlling a motor. These commands and their associated replies do not happen in a linear fashion; the timing may be reversed. Thus, a traditional producer-consumer model is not enough; this is more of a dispatcher.
例如,我们称这个单例类为Arduino
.Thread A
正在运行,想要发送一个命令"*03"
,所以它调用Arduino.Instance.SendCommand("*03")
.同时,Thread B
发送一个命令 "*88"
,这两个命令都是近实时发送的.稍后,Arduino
的 SerialPort.Read()
线程会收到对 *88
的回复,然后收到对 *03 的回复
(即以相反的顺序发送).我们如何允许 Thread A
和 Thread B
正确阻止等待特定回复的到来?我们假设我们将在每个线程中使用 AutoResetEvent
,并带有一个异步回调让我们 .Set
它.
For example, let's call this singleton class Arduino
. Thread A
is running, and wants to send a command "*03"
, so it calls Arduino.Instance.SendCommand("*03")
. Meanwhile, Thread B
sends a command "*88"
, both of which get sent in near-realtime. Sometime later the Arduino
's SerialPort.Read()
thread picks up a reply for *88
and then a reply for *03
(i.e. in the opposite order they were sent). How do we allow both Thread A
and Thread B
to block correctly waiting on the specific reply to come in? We're assuming we will use AutoResetEvent
inside each thread, with an async callback to let us .Set
it.
推荐答案
如果性能是您所追求的,并且异步处于最佳水平,我建议研究完成端口.这就是最终隐藏在 Windows 内核中的内容,而且非常棒.当我使用它们时,我使用了 C++,甚至因此发现了一个内核错误,但是我仅限于该语言.
If performance is what you're after, and async at its finest level, I suggest looking into Completion Ports. This is what is ultimately underneath, hidden in the Windows Kernel, and it's awesome. When I used them, I used C++, even found a Kernel bug because of it, but I was limited to the language.
我在 CodeProject 上看到了这篇文章,可能值得探索看看你可以在哪里进一步推进你的想法和/或使用那里的代码.
I've seen this article on CodeProject which might be worth exploring to see where you can take your idea further and/or use the code that's there.
完成端口的本质是处理回调.也就是说,一般来说,您将请求放入"队列中,当有东西到达那里时,会读取请求并读取指定的回调.事实上,这是一个队列,但就像我说的,处于最低(可管理)级别(在几乎进入金属之前).
The nature of Completion ports is to work on callbacks. That is, in general, you "put" a request in the queue, and when something lands there, the request is read and the callback specified is read. It is in fact, a queue, but like I said, at the lowest (manageable) level (before getting almost on metal).
我编写了一种带有完成端口的 FTP 服务器/客户端测试实用程序,因此基本过程是相同的 - 在 queuable<中读取和写入命令/em> 时尚.希望对您有所帮助.
I've written a sort of a FTP server/client testing utility with Completion ports, so the base process is the same - reading and writing of commands in a queuable fashion. Hope it helps.
编辑 #2: 好的,根据您的反馈和评论,这就是我要做的.我会有一个传出队列",ConcurrentQueue<Message>
.您可以通过将每条消息出列来有一个单独的线程来发送消息.注意,如果你想让它更安全"一点,我建议查看消息,发送它,然后将它出队. 无论如何,Message 类可以是内部的,看起来像这样:
EDIT #2: Ok, here's what I would do, based on your feedback and comments. I would have an "outgoing queue", ConcurrentQueue<Message>
. You can have a separate thread for sending messages by dequeueing each message. Note, if you want it a bit more "safe", I suggest peeking at the message, sending it, then dequeuing it. Anyway, the Message class can be internal, and look something like this:
private class Message {
public string Command { get; set; }
... additonal properties, like timeouts, etc. ...
}
在单例类(我称之为CommunicationService
)中,我还有一个ConcurrentBag<Action<Response>>
.现在是乐趣开始的地方:o).当一个单独的关注点想要做某事时,它会注册自己,例如,如果你有一个 TemperatureMeter
我会让它做这样的事情:
In the singleton class (I'll call it CommunicationService
), I'd also have a ConcurrentBag<Action<Response>>
. This is now where the fun starts :o). When a separate concern wants to do something, it registeres itself, for example, if you have a TemepratureMeter
I would have it do something like this:
public class TemperatureMeter {
private AutoResetEvent _signal = new AutoResetEvent(false);
public TemperatureMeter {
CommunicationService.AddHandler(HandlePotentialTemperatureResponse);
}
public bool HandlePotentialTemperatureResponse(Response response) {
// if response is what I'm looking for
_signal.Set();
// store the result in a queue or something =)
}
public decimal ReadTemperature() {
CommunicationService.SendCommand(Commands.ReadTemperature);
_signal.WaitOne(Commands.ReadTemperature.TimeOut); // or smth like this
return /* dequeued value from the handle potential temperature response */;
}
}
现在,在您的 CommunicationService 中,当您收到响应时,您只需发送一个
And now, in your CommunicationService, when you receive a response, you simply to a
foreach(var action in this._callbacks) {
action(rcvResponse);
}
瞧,关注点分离.它能更好地回答你的问题吗?
Voila, separation of concerns. Does it answer your question any better?
另一种可能的策略是将消息和回调耦合,但让回调是 Func
并且调度程序线程检查从 Func 返回的结果是否为真,然后此回调已处理.
Another possible tactic would be, to couple message and callback, but having the Callback be a Func<Response, bool>
and the dispatcher thread checks if the result returned from the Func is true, then this callback is disposed.
这篇关于提供异步串口通信的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!