需要C#线程帮助

时间:2012-04-18 11:33:21

标签: c# multithreading

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

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

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

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

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

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

提前致谢

安迪

4 个答案:

答案 0 :(得分: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的等待句柄,它在更改状态对象的SerialPortHandler属性后由Reply设置。这样你就不需要循环和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服务运行),无论当前是否存在您的服务类实例。

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

答案 1 :(得分: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;
}

答案 2 :(得分:1)

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

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

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

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

另一个是讨厌的OOB数据。如果有,请创建一个serialAPU,加载OOB数据,将状态设置为'EOOBdata'并用它调用一些'OOBevent'。

答案 3 :(得分:-1)

我建议你看一下BackgroundWorker - 班级

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