如何正确使用.NET2.0串口.BaseStream进行异步操作

15
我正在尝试使用.NET2.0 SerialPort的.BaseStream属性进行异步读写(BeginWrite / EndWrite,BeginRead / EndRead)。
我在这方面有一些成功经验,但是一段时间后,我注意到(使用Process Explorer),应用程序使用的句柄会逐渐增加,并偶尔会出现额外的线程,这也会增加句柄计数。
每次出现新线程时,上下文切换率也会增加。
该应用程序不断向PLC设备发送3个字节,并返回大约800个字节,并以波特率57600进行操作。
最初的CSwitch Delta(同样来自Process Explorer)约为2500,无论如何都似乎很高。每次出现新线程时,此值都会增加,并相应地增加CPU负载。
我希望有人可能已经做过类似的事情,并且可以帮助我,或者甚至说“天哪,不要那样做”。
在下面的代码中,“this._stream”是从SerialPort.BaseStream获取的,并且CommsResponse是我用作IAsyncresult状态对象的类。
我使用TCP连接作为替代使用串行端口的常见代码(我有一个CommsChannel基类,派生自它的是串行和TCP通道),并且它没有这些问题,因此我有理由相信CommsResponse类没有任何问题。
任何意见都将不胜感激。
    /// <summary>
    /// Write byte data to the channel.
    /// </summary>
    /// <param name="bytes">The byte array to write.</param>
    private void Write(byte[] bytes)
    {
        try
        {
            // Write the data to the port asynchronously.
            this._stream.BeginWrite(bytes, 0, bytes.Length, new AsyncCallback(this.WriteCallback), null);
        }
        catch (IOException ex)
        {
            // Do stuff.
        }
        catch (ObjectDisposedException ex)
        {
            // Do stuff.
        }
    }

    /// <summary>
    /// Asynchronous write callback operation.
    /// </summary>
    private void WriteCallback(IAsyncResult ar)
    {
        bool writeSuccess = false;

        try
        {
            this._stream.EndWrite(ar);
            writeSuccess = true;
        }
        catch (IOException ex)
        {
            // Do stuff.
        }

        // If the write operation completed sucessfully, start the read process.
        if (writeSuccess) { this.Read(); }
    }

    /// <summary>
    /// Read byte data from the channel.
    /// </summary>
    private void Read()
    {
        try
        {
            // Create new comms response state object.
            CommsResponse response = new CommsResponse();

            // Begin the asynchronous read process to get response.
            this._stream.BeginRead(this._readBuffer, 0, this._readBuffer.Length, new AsyncCallback(this.ReadCallback), response);
        }
        catch (IOException ex)
        {
            // Do stuff.
        }
        catch (ObjectDisposedException ex)
        {
            // Do stuff.
        }
    }

    /// <summary>
    /// Asynchronous read callback operation.
    /// </summary>
    private void ReadCallback(IAsyncResult ar)
    {
        // Retrieve the comms response object.
        CommsResponse response = (CommsResponse)ar.AsyncState;

        try
        {
            // Call EndRead to complete call made by BeginRead.
            // At this point, new data will be in this._readbuffer.
            int numBytesRead = this._stream.EndRead(ar);

            if (numBytesRead > 0)
            {
                // Create byte array to hold newly received bytes.
                byte[] rcvdBytes = new byte[numBytesRead];

                // Copy received bytes from read buffer to temp byte array
                Buffer.BlockCopy(this._readBuffer, 0, rcvdBytes, 0, numBytesRead);

                // Append received bytes to the response data byte list.
                response.AppendBytes(rcvdBytes);

                // Check received bytes for a correct response.
                CheckResult result = response.CheckBytes();

                switch (result)
                {
                    case CheckResult.Incomplete: // Correct response not yet received.
                        if (!this._cancelComm)
                        {
                            this._stream.BeginRead(this._readBuffer, 0, this._readBuffer.Length,
                                new AsyncCallback(this.ReadCallback), response);
                        }
                        break;

                    case CheckResult.Correct:  // Raise event if complete response received.
                        this.OnCommResponseEvent(response);
                        break;

                    case CheckResult.Invalid: // Incorrect response
                        // Do stuff.
                        break;

                    default: // Unknown response
                        // Do stuff.
                        break;
                }
            }
            else
            {
                // Do stuff.
            }
        }
        catch (IOException ex)
        {
            // Do stuff.
        }
        catch (ObjectDisposedException ex)
        {
            // Do stuff.
        }
    }
4个回答

5

一些建议:

由于您只发送3个字节,所以可以使用同步写操作。延迟不会是一个问题。

此外,不要每次都创建新的AsyncCallback。创建一个读取和一个写入AsyncCallback,并在每个开始调用中使用它。


1
谢谢您的回复。关于创建回调的建议非常好。我已经尝试过了,但是处理/线程/上下文切换速率仍然会增加,尽管似乎增加速度较慢,这是一种改进。 - Andy

4
不需要使用BeginWrite。你只发送3个字节,它们很容易适应传输缓冲区,并且在发送下一组数据时,始终确保缓冲区为空。
请记住,串行端口比TCP/IP连接慢得多。很可能每接收一个字节都会调用BeginRead()。这会让线程池忙碌起来,你肯定会看到很多上下文切换。关于句柄消耗就不太确定了。一定要在没有调试器附加的情况下进行测试。
尝试使用DataReceived而不是BeginRead()肯定是你应该尝试的。拉取而不是推送,当有事情发生时,你将使用一个线程池线程,而不是总是有一个活动的线程。

我理解BeginWrite并不是必须的。虽然BeginRead不会为每个字节触发,但它确实相当频繁地触发。也许我可以使用DataReceived并将字节传递到自己的流中以保持类的一致性?顺便说一下,我也在测试“发布”版本。 - Andy
@Hans:正如Andy所说,BeginRead/EndRead 可以轻松处理每次调用中的多个字节(取决于超时设置)。而S.IO.P.SerialPort始终会阻塞线程池线程以侦测数据并触发DataReceived,因此你想象的资源使用更低。实际上,与检测活动然后读取解决方案相比,BeginRead/EndRead 需要更少的内核调用。 - Ben Voigt

0

设备的响应是否总是固定大小?如果是,请尝试使用SerialPort.Read并传递数据包大小。这将会阻塞,因此请与DataReceived结合使用。更好的方法是,如果响应总是以相同的字符结尾,并且保证该结束标志在数据包中是唯一的,则设置NewLine属性并使用ReadLine。这将使您免受未来数据包大小更改的影响。


数据包大小不幸是可变的。 - Andy
糟糕。那么你如何知道何时收到完整的数据包? - mtrw
数据包包含头部和终止字节,其中编码了数据包的长度和校验和。 - Andy
如果保证终止符不会在有效载荷中使用,您可以设置 NewLine 并使用 ReadLine - mtrw
不幸的是(再次!)数据包包含二进制字节数据,而不是ASCII码,因此从0x0到0xFF的所有值都是有效的。感谢您的建议。 - Andy

0

能否将从串行端口传入的数据直接发送到文件中?在高波特率(1兆波特/秒)下,处理如此大量连续数据很困难。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接