Как правильно использовать последовательный порт .NET2.0.BaseStream для асинхронной работы

Я пытаюсь использовать свойство .BaseStream для .NET2.0 SerialPort для асинхронного чтения и записи (BeginWrite/EndWrite, BeginRead/EndRead).

У меня есть некоторый успех в этом, но через некоторое время я замечаю (используя Process Explorer) очень постепенное увеличение Handles, которое использует приложение, а иногда и дополнительный поток, который также увеличивает количество Handle.

Скорость переключения контекста также увеличивается при каждом появлении нового потока.

Приложение постоянно отправляет 3 байта на устройство ПЛК и получает в итоге 800 или около того байтов и делает это со скоростью в 57600.

Начальная CSwitch Delta (опять же, из Process Explorer) составляет около 2500, что в любом случае кажется очень высоким. Каждый раз, когда появляется новый поток, это значение увеличивается, и соответственно увеличивается загрузка ЦП.

Я надеюсь, что кто-то может сделать что-то подобное, и может помочь мне, или даже сказать: "Во имя Бога, не делайте этого таким образом".

В приведенном ниже коде "this._stream" получается из SerialPort.BaseStream, а CommsResponse - это класс, который я использую как объект состояния IAsyncresult.

Этот код является общим для TCP-соединения, которое я делаю в качестве альтернативы использованию последовательного порта (у меня есть базовый класс CommsChannel, с его производным от последовательного и TCP-канала), и у него нет ни одной из этих проблем, m разумно надеюсь, что нет ничего плохого в классе 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.
        }
    }

Ответы

Ответ 1

Некоторые предложения:

Поскольку вы отправляете только 3 байта, вы можете иметь синхронную операцию записи. Задержка не будет большой проблемой.

Также не создавайте новый AsyncCallback все время. Создайте один Read и One Write AsyncCallback и используйте это при каждом начале вызова.

Ответ 2

Нет необходимости вообще в BeginWrite. Вы отправляете только 3 байта, они легко вписываются в буфер передачи, и вы всегда уверены, что буфер пуст, когда вы отправляете следующий набор.

Имейте в виду, что последовательные порты намного медленнее, чем соединения TCP/IP. Вполне вероятно, что вы в конечном итоге вызываете BeginRead() для каждого байта, который вы получаете. Это дает пулу потоков хорошую тренировку, вы определенно увидите много переключателей контекста. Не так уверен в потреблении рукоятки. Не забудьте проверить, не прилагается ли отладчик.

Попытка DataReceived вместо BeginRead() - это то, что вы должны попробовать. Потяните вместо push, вы будете использовать threadpool-поток, если что-то происходит, а не всегда с одним активным.

Ответ 3

Можно ли принимать данные, поступающие из последовательного порта, и напрямую отправлять их в файл? При высоких скоростях передачи (1 MegaBaud) трудно обрабатывать это количество безостановочных данных.

Ответ 4

Является ли ответ от устройства постоянным размером? Если да, попробуйте использовать SerialPort.Read и передать размер пакета. Это будет заблокировано, поэтому объедините его с DataReceived. Еще лучше, если ответ всегда заканчивается одним и тем же символом (ов), и эта завершающая подпись гарантированно будет уникальной в пакете, установите NewLine и использовать ReadLine. Это будет иммунизировать вас от будущих изменений размера пакета.