Как правильно использовать последовательный порт .NET2.0 .BaseStream для асинхронной операции - PullRequest
15 голосов
/ 20 января 2009

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

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

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

Приложение постоянно отправляет 3 байта на устройство PLC и получает около 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.
        }
    }

Ответы [ 4 ]

5 голосов
/ 23 января 2009

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

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

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

4 голосов
/ 24 января 2009

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

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

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

0 голосов
/ 31 мая 2011

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

0 голосов
/ 26 марта 2009

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

...