目前,我们的应用程序通过串口连接到Arduino。我们发送一些ASCII格式的命令,并得到相同的回报。为此,我们有一个命令队列,一个专门用于将这些命令写入端口的线程,以及一个专门用于读取和处理所有传入回复的线程。类本身负责调度回复,这给了它太多的责任(应该只负责端口操作,而不是业务逻辑)。
我们宁愿以异步方式执行此操作。系统中的任何内容都可以发送带有回调函数和超时的命令。如果串口获得正确的回复,则会调用回调函数。否则,它会超时并可能调用第二个回调(或者可能是一个带有succeeded?
标志的回调。)
但是,我们只使用异步方法(特别是在Web操作中),而不是编写这样的系统。谁能给我们一些关于如何进行的指示?
我们当前的计划是存储这些命令的队列。在任何回复时,如果找到相关联的命令(通过比较ASCII值),则将其出列并执行回调。计时器将定期检查超时,出队并执行适当的回调。这似乎是一个简单的解决方案,但支持这一点的代码量正在大幅增加,我们希望确保没有更好的内置解决方案或最佳实践。
编辑:为了进一步说明,这个特定的类是单例(无论好坏),还有许多其他线程可以访问它。例如,一个线程可能想要请求传感器值,而另一个线程可能正在控制电机。这些命令及其相关的回复不是以线性方式发生的;时机可能会逆转。因此,传统的生产者 - 消费者模式是不够的;这更像是一个调度员。
例如,让我们调用这个单例类Arduino
。 Thread A
正在运行,并且想要发送命令"*03"
,因此它会调用Arduino.Instance.SendCommand("*03")
。同时,Thread B
发送命令"*88"
,这两个命令都是近乎实时发送的。稍后,Arduino
的{{1}}主题获取SerialPort.Read()
的回复,然后回复*88
(即以相反的顺序发送)。我们如何允许*03
和Thread A
阻止正确等待特定回复进入?我们假设我们将在每个线程中使用Thread B
,并使用异步回调让我们AutoResetEvent
。
答案 0 :(得分:3)
如果性能是您所追求的,并且异步处于最佳水平,我建议您查看完成端口。这是最终隐藏在Windows内核中的内容,它非常棒。当我使用它们时,我使用了C ++,甚至因为它而发现了一个内核错误,但是我仅限于该语言。
我在CodeProject上看过this article,可能值得探索一下,看看你可以在哪里进一步理解和/或使用那里的代码。
完成端口的本质是处理回调。也就是说,通常,您将一个请求“放入”队列中,当某些内容落在那里时,将读取请求并读取指定的回调。事实上,这是一个队列,但就像我说的那样,处于最低(可管理)的水平(在接近金属之前)。
编辑:我编写了一种带有完成端口的FTP服务器/客户端测试实用程序,因此基本过程是相同的 - 在 queuable <中读取和写入命令/ em>时尚。希望它有所帮助。
编辑#2:好的,根据您的反馈和评论,这就是我要做的。我会有一个“传出队列”,ConcurrentQueue<Message>
。您可以通过出列每个消息来使用单独的线程来发送消息。 注意,如果你想要它更“安全”,我建议偷看邮件,发送它,然后将它出列。无论如何,Message类可以是内部的,看起来像这样: / p>
private class Message {
public string Command { get; set; }
... additonal properties, like timeouts, etc. ...
}
在单身人士课程中(我称之为CommunicationService
),我也有一个ConcurrentBag<Action<Response>>
。这就是现在开始的乐趣:o)。当一个单独的关注想要做某事时,它会注册自己,例如,如果你有一个TemepratureMeter
,我会让它做这样的事情:
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中,当您收到回复时,您只需要
foreach(var action in this._callbacks) {
action(rcvResponse);
}
Voila,关注点分离。它会更好地回答你的问题吗?
另一个可能的策略是,结合消息和回调,但让Callback为Func<Response, bool>
并且调度程序线程检查从Func返回的结果是否为真,那么此回调是 dispos < / em>的
答案 1 :(得分:1)
如果使用4.0,更好的选择是使用BlockingCollection
,对于旧版本,使用Queue<T>
和AutoResetEvent
的组合。因此,当添加项目并在消费者线程上然后消费它时,您将通知。在这里,我们正在使用推送技术,在您当前的实现中,您每次询问是否有任何数据时都使用轮询技术。
示例:4.0
//declare the buffer
private BlockingCollection<Data> _buffer = new BlockingCollection<Data>(new ConcurrentQueue<Data>());
//at the producer method "whenever you received an item":
_messageBuffer.Add(new Data());
//at the consumer thread "another thread(s) that is running without to consume the data when it arrived."
foreach (Data data in _buffer.GetConsumingEnumerable())// or "_buffer.Take" it will block here automatically waiting from new items to be added
{
//handle the data here.
}
示例:其他“较低”版本:
private ConcurrentQueue<Data> _queue = new ConcurrentQueue<Data>();
private AutoResetEvent _queueNotifier = new AutoResetEvent(false);
//at the producer:
_queue.Enqueue(new Data());
_queueNotifier.Set();
//at the consumer:
while (true)//or some condition
{
_queueNotifier.WaitOne();//here we will block until receive signal notification.
Data data;
if (_queue.TryDequeue(out data))
{
//handle the data
}
}