2

我被要求编写一个方法,允许调用者通过串行端口向硬件设备发送命令字符串。发送命令后,该方法必须等待设备的响应,然后将其返回给调用者。

更复杂的是,硬件设备会定期向 PC 发送未经请求的数据包(应用程序必须存储以进行报告的数据)。所以当我发送一个串口命令时,我可能会在收到命令响应之前收到一个或多个数据包。

其他注意事项:可能有多个客户端可能同时发送串行命令,因为此方法将构成 WCF 服务的基础。此外,该方法需要是同步的(出于我不会在这里讨论的原因),因此排除了使用回调将响应返回给客户端的可能性。

关于“多个客户端”,我计划使用 BlockingCollection<> 对传入命令进行排队,并使用一个后台线程一次执行一个任务,从而避免串行端口争用。

但是我不确定如何处理传入的串行数据。我最初的想法是有另一个后台线程不断读取串行端口,存储数据分析包,但也寻找命令响应。当接收到一个时,线程会以某种方式将响应数据返回给最初发送串行命令的方法(从那时起它一直在等待 - 请记住我有一个规定,该方法是同步的)。

这是我不确定的最后一点 - 我怎样才能让我的方法等到后台线程收到命令的响应?以及如何将后台线程的响应传递给我的等待方法,以便它可以将其返回给调用者?我是线程新手,所以我会以错误的方式解决这个问题吗?

提前致谢

安迪

4

4 回答 4

2

首先:当你使用SerialPort框架自带的类时,数据接收事件已经是异步的了。当你发送一些东西时,数据是异步传入的。

我要尝试的是:将所有需要等待答案的请求排队。在整个接收处理程序中,检查传入数据是否是其中一个请求的答案。如果是这样,将回复与请求信息一起存储(为此创建某种状态类)。所有其他传入数据均正常处理。

那么,如何让请求等待答复呢?发送命令并返回回复的调用将创建状态对象,将其排队并监视对象以查看是否收到了答案。如果收到应答,则调用返回结果。

一个可能的大纲可以是:

string SendAndWait(string command)
{
    StateObject state = new StateObject(command);
    state.ReplyReceived = new ManualResetEvent(false);
    try
    {
        SerialPortHandler.Instance.SendRequest(command, state);
        state.ReplyReceived.WaitOne();
    }
    finally
    {
        state.ReplyReceived.Close();
    }

    return state.Reply;
}

是什么SerialPortHandler?我会让它成为一个单例类,其中包含一个Instance访问单例实例的属性。这个类完成了所有的串口工作。它还应该包含当“带外”信息进入时引发的事件(不是对命令的回复的数据)。

它还包含SendRequest将命令发送到串行设备、将状态对象存储在内部列表中、等待命令的回复进入并使用回复更新状态对象的方法。

状态对象包含一个调用的等待句柄,它在更改状态对象的属性后ReplyReceived由 设置。这样你就不需要循环和. 此外,您可以调用几毫秒来等待回复进来,而不是调用。这样,您可以实现某种超时功能。SerialPortHandlerReplyThread.SleepWaitOne()WaitOne(timeout)timeout

这就是它的外观SerialPortHandler

void HandlePossibleCommandReply(string reply)
{
    StateObject state = FindStateObjectForReply(reply);
    if (state != null)
    {
        state.Reply = reply;
        state.ReplyReceived.Set();

        m_internalStateList.Remove(state);
    }
}

请注意:这是我尝试开始的。我确信这可以得到非常优化,但正如您所见,其中并没有涉及太多“多线程”——只有该SendAndWait方法应该以某种方式调用,以便多个客户端可以发出命令,而另一个客户端仍在等待它的响应。

编辑
另一个注意事项:您是说该方法应构成 WCF 服务的基础。这使事情变得更容易,就像您配置服务一样,将为每次调用服务创建一个服务类的实例,因此该SendAndWait方法将“活”在它自己的服务实例中,甚至不需要完全可以重入。在这种情况下,您只需要确保SerialPortHandler始终处于活动状态(=> 是独立于实际 WCF 服务创建和运行的),无论当前是否存在您的服务类的实例。

编辑 2
我按照评论中的建议将示例代码更改为不循环和睡眠。

于 2012-04-18T11:55:51.807 回答
1

如果您真的想阻塞直到后台线程收到您的命令响应,您可以考虑让后台线程在您将命令排入队列并将其返回给您时锁定一个对象。接下来,您等待锁定并继续:

// in main code:
var locker = mySerialManager.Enquee(command);
lock (locker)
{
     // this will only be executed, when mySerialManager unlocks the lock
}

// in SerialManager
public object Enqueue(object command)
{
    var locker = new Object();
    Monitor.Enter(locker); 
    // NOTE: Monitor.Exit() gets called when command result 
    // arrives on serial port
    EnqueueCommand(command, locker);
    return locker;
}
于 2012-04-18T11:53:41.467 回答
1

几件事。您需要能够将串行响应绑定到请求它们的命令。我假设有一些索引或序列号与命令一起出现并在响应中返回?

鉴于此,你应该没问题。您需要某种“serialAPU”类来表示请求和响应。我不知道这些是什么,也许只是字符串,我不知道。该类也应该有一个 autoResetEvent。无论如何,在你的'DoSerialProtocol()'函数中,创建一个serialAPU,用请求数据加载它,将它排入串行线程并等待autoResetEvent。当线程获得serialAPU时,它可以在serialAPU中存储一个索引/序列号,将serialAPU存储在一个向量中并发送请求。

当数据进入时,您是否进行协议处理,如果数据是有效响应,则从数据中获取索引/序列并在向量中的 serialAPU 中查找匹配值。从向量中删除匹配的serialAPU,用响应数据加载它并发出autoResetEvent信号。最初调用“DoSerialProtocol()”的线程将继续运行并可以处理响应数据。

当然,有很多“摆动”。超时是其中之一。我很想在 serialAPU 中有一个状态枚举,受 CritcalSection 或 atomicCompareandSwap 保护,初始化为“Esubmitted”。如果起始线程在 autoResetEvent 上的等待超时,它会尝试将其 serialAPU 中的状态枚举设置为“EtimedOut”。如果成功,很好,它会向调用者返回一个错误。同样,在串行线程中,如果它找到状态为 EtimedOut 的串行 APU,它只是将其从容器中删除。如果找到与响应数据匹配的serialAPU,它会尝试将状态更改为“EdataRx”,如果成功。触发 autoRestEvent。

另一个是烦人的OOB数据。如果出现这种情况,创建一个串行 APU,加载 OOB 数据,将状态设置为“EOOBdata”并用它调用一些“OOBevent”。

于 2012-04-18T11:55:03.390 回答
-1

我建议你看看BackgroundWorker -Class

此类中有一个事件(RunWorkerCompleted),当工人完成工作时会触发该事件。

于 2012-04-18T11:40:01.383 回答