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

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

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

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

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

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

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

В приведенном ниже коде 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.
        }
    }

person Andy    schedule 20.01.2009    source источник


Ответы (4)


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

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

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

person kgiannakakis    schedule 23.01.2009
comment
Спасибо за ваш ответ. Хорошее предложение по созданию обратных вызовов. Я пробовал, но скорость переключения дескриптора/потока/контекста по-прежнему увеличивается, хотя кажется, что она увеличивается медленнее, так что это улучшение. - person Andy; 23.01.2009

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

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

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

person Hans Passant    schedule 24.01.2009
comment
Я беру точку в отношении BeginWrite, это на самом деле не нужно. BeginRead не срабатывает для каждого байта, но срабатывает довольно часто. Может быть, я мог бы использовать DataReceived и передавать байты в собственный поток, чтобы сохранить согласованность класса? Кстати, я также тестирую версию «Release». - person Andy; 24.01.2009
comment
@Hans: Как сказал Энди, BeginRead/EndRead легко обрабатывает несколько байтов за вызов (в зависимости от настройки тайм-аута). А S.IO.P.SerialPort постоянно блокирует поток пула потоков, чтобы обнаруживать данные и запускать DataReceived, поэтому вы представляете себе более низкое использование ресурсов. На самом деле для BeginRead/EndRead требуется меньше вызовов ядра, чем при использовании решения «обнаружить активность, а затем прочитать». - person Ben Voigt; 29.12.2016

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

person Community    schedule 26.03.2009

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

person mtrw    schedule 30.05.2011
comment
К сожалению, размеры пакетов варьируются. - person Andy; 31.05.2011
comment
облом. Так как же узнать, что вы получили полный пакет? - person mtrw; 31.05.2011
comment
Пакет содержит байты заголовка и терминатора, а также длину пакета и контрольную сумму, закодированную в нем. - person Andy; 31.05.2011
comment
Если терминатор гарантированно не будет использоваться в полезной нагрузке, вы можете установить NewLine и использовать ReadLine. - person mtrw; 31.05.2011
comment
К сожалению (опять же!) пакет содержит двоичные байтовые данные, а не ASCII, и поэтому допустимы все значения от 0x0 до 0xFF. Спасибо за ваши предложения. - person Andy; 31.05.2011