Как правильно использовать последовательный порт .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.
        }
    }
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
15
0
11 738
4

Ответы 4

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

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

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

Спасибо за ваш ответ. Хорошее предложение относительно создания обратных вызовов. Я пробовал это, но скорость переключения дескриптора / потока / контекста все еще увеличивается, хотя кажется, что она увеличивается медленнее, поэтому улучшение.

Andy 23.01.2009 19:33

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

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

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

Я беру точку, касающуюся BeginWrite, на самом деле в этом нет необходимости. BeginRead запускается не для каждого байта, но запускается довольно часто. Может быть, я мог бы использовать DataReceived и передать байты в собственный поток, чтобы класс оставался согласованным? Кстати, я тестирую и «Релизную» версию.

Andy 24.01.2009 12:27

@Hans: Как сказал Энди, BeginRead / EndRead легко обрабатывает несколько байтов за вызов (в зависимости от настройки тайм-аута). И S.IO.P.SerialPort постоянно блокирует поток пула потоков, чтобы обнаруживать данные и запускать DataReceived, поэтому вы представляете себе меньшее использование ресурсов. На самом деле для BeginRead / EndRead требуется меньше вызовов ядра, чем для решения "обнаружение активности, затем чтение".

Ben Voigt 29.12.2016 00:08

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

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

К сожалению, размеры пакетов варьируются.

Andy 31.05.2011 00:48

Облом. Итак, как узнать, что вы получили полный пакет?

mtrw 31.05.2011 00:54

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

Andy 31.05.2011 14:17

Если гарантировано, что терминатор не будет использоваться в полезной нагрузке, вы можете установить NewLine и использовать ReadLine.

mtrw 31.05.2011 20:27

К сожалению (опять же!) Пакет содержит двоичные байтовые данные, а не ASCII, и поэтому все значения от 0x0 до 0xFF действительны. Тем не менее, спасибо за ваши предложения.

Andy 31.05.2011 21:20

Другие вопросы по теме