2012-04-18 139 views
2

我被要求编写一个方法,允许调用者通过串口向硬件设备发送命令字符串。发送命令后,方法必须等待设备的响应,然后返回给调用者。需要C#线程帮助

使硬件设备周期性地向PC发送未经请求的数据包(应用程序必须存储用于报告的数据)变得复杂。所以当我发送一个串行命令时,我可能会收到一个或多个数据包,然后收到命令响应。

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

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

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

这是最后一点我不确定 - 我怎么让我的方法等待,直到后台线程收到命令的响应?我如何将来自后台线程的响应传递给我的等待方法,以便它可以将它返回给调用方?我是线程新手,所以我以这种错误的方式去做?

在此先感谢

安迪

+0

不要“等待”退货。你在一个事件驱动的系统。 – 2012-04-18 11:36:03

+0

来自设备的数据是否具有某种请求ID,以便知道数据的用途? – 2012-04-18 11:36:54

+1

http://en.wikipedia.org/wiki/Actor_model!正如@亨克所说,使其成为基础,大部分的复杂性消失。 – Travis 2012-04-18 11:56:29

回答

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的等待句柄,它在更改状态对象的Reply属性后由SerialPortHandler设置。这样你就不需要循环和Thread.Sleep。另外,不要致电WaitOne(),您可以拨打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服务创建和运行的),无论目前是否有服务类实例。

EDIT 2
我改变了我的示例代码不循环和睡眠作为意见提出。

+0

听起来很好,然后我看到睡眠(100)。不过,状态对象很好。在我的回答中,我称它为'serialAPU',这基本上就是你的建议。我认为你错过的是一个同步,(也许autoResetEvent),在初始线程等待的状态对象。 – 2012-04-18 11:59:48

+0

嗯,我不认为同步在这里是一个很大的问题,但我会尝试将其纳入我的答案。 – 2012-04-18 12:02:14

+0

我想这不是真正的同步,只是信号。事情是,OS书籍坚持要求事件,信号量,互斥等'同步对象'。 – 2012-04-18 12:04:20

-1

我劝你看看BackgroundWorker -Class

疗法是在这个类(RunWorkerCompleted)当工人完成其发射的事件他工作。

+0

BackgroundWorker在每种情况下都不是正确的工具 - 而且这种情况似乎是后台工作人员不是正确的工具之一... – 2012-04-18 11:46:07

+0

我在@ThorstenDittmar上。我非常不喜欢这种方法,因此它应该得到反对票。 – 2012-04-18 11:48:15

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

一对夫妇。您需要能够将串行响应与请求它们的命令捆绑在一起。我认为有一些索引或序列号与命令一起出现并返回到响应中?

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

当数据进来时,你是否协议的东西,如果数据是一个有效的响应,从数据获得索引/序列,并查找vectorAPU中的匹配值。从矢量中删除匹配的serialAPU,将其载入响应数据并发出autoResetEvent信号。调用'DoSerialProtocol()'的线程最初将运行并处理响应数据。

当然有很多'wiggles'。超时是一个。我会试图在serialAPU中拥有一个状态枚举,由CritcalSection或atomicCompareandSwap保护,初始化为'Esubmitted'。如果oringinating线程超时等待autoResetEvent,它会尝试将serialAPU中的状态枚举设置为'EtimedOut'。如果成功,很好,它会给调用者返回一个错误。同样,在串行线程中,如果它发现状态为EtimedOut的serialAPU,它只是将它从容器中移除。如果它找到匹配响应数据的serialAPU,它会尝试将状态更改为'EdataRx',如果成功。激发autoRestEvent。

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