Я пытаюсь использовать свойство .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.
}
}





Некоторые предложения:
Поскольку вы отправляете только 3 байта, у вас может быть синхронная операция записи. Задержка не будет большой проблемой.
Также не создавайте новый AsyncCallback все время. Создайте один AsyncCallback для чтения и один для записи и используйте их при каждом вызове begin.
Нет необходимости в BeginWrite. Вы отправляете только 3 байта, они легко помещаются в буфер передачи, и вы всегда уверены, что буфер пуст, когда вы отправляете следующий набор.
Имейте в виду, что последовательные порты намного медленнее, чем соединения TCP / IP. Весьма вероятно, что вы в конечном итоге вызовете BeginRead () для каждого полученного байта. Это дает пулу потоков хорошую тренировку, вы определенно увидите много переключений контекста. Не уверен в расходе ручки. Обязательно проверьте это без подключенного отладчика.
Вам определенно стоит попробовать DataReceived вместо BeginRead (). Pull вместо push, вы будете использовать поток threadpool, когда что-то происходит, вместо того, чтобы всегда иметь один активный.
Я беру точку, касающуюся BeginWrite, на самом деле в этом нет необходимости. BeginRead запускается не для каждого байта, но запускается довольно часто. Может быть, я мог бы использовать DataReceived и передать байты в собственный поток, чтобы класс оставался согласованным? Кстати, я тестирую и «Релизную» версию.
@Hans: Как сказал Энди, BeginRead / EndRead легко обрабатывает несколько байтов за вызов (в зависимости от настройки тайм-аута). И S.IO.P.SerialPort постоянно блокирует поток пула потоков, чтобы обнаруживать данные и запускать DataReceived, поэтому вы представляете себе меньшее использование ресурсов. На самом деле для BeginRead / EndRead требуется меньше вызовов ядра, чем для решения "обнаружение активности, затем чтение".
Можно ли взять данные, поступающие из последовательного порта, и напрямую отправить их в файл? При высоких скоростях передачи данных (1 мегабит) трудно обрабатывать такой объем непрерывных данных.
Всегда ли ответ от устройства имеет фиксированный размер? Если это так, попробуйте использовать SerialPort.Read и передать размер пакета. Это заблокирует, поэтому объедините его с DataReceived. Еще лучше, если ответ всегда заканчивается одним и тем же символом (символами) и эта конечная подпись гарантированно уникальна в пакете, установите свойство NewLine и используйте ReadLine. Это защитит вас от будущих изменений размера пакета.
К сожалению, размеры пакетов варьируются.
Облом. Итак, как узнать, что вы получили полный пакет?
Пакет содержит байты заголовка и терминатора, в которых закодированы длина пакета и контрольная сумма.
Если гарантировано, что терминатор не будет использоваться в полезной нагрузке, вы можете установить NewLine и использовать ReadLine.
К сожалению (опять же!) Пакет содержит двоичные байтовые данные, а не ASCII, и поэтому все значения от 0x0 до 0xFF действительны. Тем не менее, спасибо за ваши предложения.
Спасибо за ваш ответ. Хорошее предложение относительно создания обратных вызовов. Я пробовал это, но скорость переключения дескриптора / потока / контекста все еще увеличивается, хотя кажется, что она увеличивается медленнее, поэтому улучшение.