需要 C# 线程帮助

本文关键字:帮助 线程 需要 | 更新日期: 2023-09-27 18:31:41

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

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

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

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

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

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

提前致谢

安 迪

需要 C# 线程帮助

首先:当您使用框架附带的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(timeout)而不是调用WaitOne()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
我将示例代码更改为不循环和睡眠,如注释中的建议。

如果您确实想在后台线程收到您的命令响应之前阻止,您可以考虑让后台线程在将命令排队并将其返回给您时锁定对象。接下来,等待锁定并继续:

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

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

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

当数据进来时,你是否协议的东西,如果数据是有效的响应,从数据中获取索引/序列,并在向量中的串行APU中查找匹配值。 从向量中删除匹配的串行APU,加载响应数据并发出自动重置事件的信号。 最初称为"DoSerialProtocol()"的线程将在其上运行,并且可以处理响应数据。

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

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

我建议你看看BackgroundWorker-Class

Ther 是此类中的一个事件 (RunWorkerComplete),当工作线程完成其工作时触发。