views:

2582

answers:

3

I am attempting to use the .BaseStream property of the .NET2.0 SerialPort to do asynchronous reads and writes (BeginWrite/EndWrite, BeginRead/EndRead).

I am having some success in this, but after a time, I notice (using Process Explorer) a very gradual increase in the Handles the app is using, and occasionally an extra thread, which also increases the Handle count.

The context switch rate also increases each time a new thread appears.

The app constantly sends 3 bytes to a PLC device, and gets 800 or so bytes in return, and does so at a baud rate of 57600.

The initial CSwitch Delta (again, from Process Explorer) is around 2500, which seems very high anyway. Each time a new thread appears, this value increases, and the CPU load increases accordingly.

I'm hoping that somebody might have done something similar, and can help me out, or even say 'In God's name, don't do it that way.'

In the code below, 'this._stream' is obtained from SerialPort.BaseStream, and CommsResponse is a class I use as the IAsyncresult state object.

This code is common to a TCP connection I make as an alternative to using the serial port, (I have a CommsChannel base class, with a serial and TCP channel derived from it) and it has none of these problems so I'm reasonably hopeful that there is nothing wrong with the CommsResponse class.

Any comments very, very gratefully received as I am starting to lose hair over the @$!# .NET serial port!

    /// <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.
        }
    }
+3  A: 

Some suggestions:

Since you are only sending 3 bytes, you could have a synchronous Write Operation. The delay wouldn't be much of an issue.

Also don't create a new AsyncCallback all the time. Create one Read and one Write AsyncCallback and use that in every begin call.

kgiannakakis
Thank you for your reply. Good suggestion regarding the creation of the Callbacks. I have tried it, but the handle/thread/context switch rate still increases, although it does appear to increase at a slower rate, so an improvement.
Andy
+3  A: 

No need at all for BeginWrite. You only send 3 bytes, they'll easily fit in the transmit buffer, and you're always sure that the buffer is empty when you send the next set.

Keep in mind that serial ports are much slower than TCP/IP connections. It is pretty likely that you end up calling BeginRead() for every single byte that you receive. That gives the thread pool a good workout, you'd definitely see a lot of context switches. Not so sure about handle consumption. Be sure to test that without the debugger attached.

Trying DataReceived instead of BeginRead() is definitely something you should try. Pull instead of push, you'll use a threadpool thread when there's something happening instead of always having one active.

Hans Passant
I take the point with regard to BeginWrite, it is not really necessary. BeginRead doesn't trigger for every byte, but it does trigger pretty frequently. Maybe I could use DataReceived and pass the bytes into a stream of my own to keep the class consistent? I am testing a 'Release' version too, BTW.
Andy
A: 

Is it possible to take the data comming in from the serial port and directly send it to a file? At high a baud rates (1 MegaBaud) it is difficult to handle this amount of non-stop data.