Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[98.84.25.165] |
|
Сообщ.
#1
,
|
|
|
Доброго времени суток!
Я достаточно много лет использую компонент TComPort by Dejan Crnila. Баловался с факс-модемами, мобилками, было несколько серьезных разработок. Никаких проблем не возникало. И вот однажды стали возникать проблемы, проявляющиеся в виде зависания наглухо программы, управляющей механическим оборудованием. Не буду вдаваться в подробности, все команды сводятся к вызову ComPort.Write() и последующему ComPort.Read(). Пакеты данных формируются особым образом, проверяется контрольная сумма, железо не глючная ну и так далее. Максимальный режим дебага ничего не показал, пришлось залазить в исходники компонента и модифицировав задуманные разработчиками возможности CALLTRACE (модифицировал OutputDebugString) получил следующие выводы: TCustomComPort.Read у меня в следующем виде: // perform synchronous read operation function TCustomComPort.Read(Buffer:TCPortBytes; Count: Integer): Integer; var AsyncPtr: PCPortAsync; begin {$ifdef CPORT_CALLTRACE} OutputDebugString('TCustomComPort.Read '+inttostr(Count)+' bytes'); {$endif} CportInitAsync(AsyncPtr); try {$ifdef CPORT_CALLTRACE} OutputDebugString('>> Before ReadAsync'); {$endif} ReadAsync(Buffer, Count, AsyncPtr); {$ifdef CPORT_CALLTRACE} OutputDebugString('>> After ReadAsync'); {$endif} Result := WaitForAsync(AsyncPtr); {$ifdef CPORT_CALLTRACE} OutputDebugString('>> After WaitForAsync'); {$endif} finally CportDoneAsync(AsyncPtr); end; end; Из нее вызывается функция // wait for asynchronous operation to end function TCustomComPort.WaitForAsync(var AsyncPtr: PCPortAsync): Integer; var BytesTrans, Signaled: DWORD; Success: Boolean; begin if AsyncPtr = nil then raise EComPort.Create(FPort,CError_InvalidAsync); {$ifdef CPORT_CALLTRACE} OutputDebugString(PChar('TCustomComPort.WaitForAsync WaitForSingleObject! '+ IntToHex(Integer(AsyncPtr^.Overlapped.hEvent),8)) ); {$endif} //Signaled := WaitForSingleObject(AsyncPtr^.Overlapped.hEvent, INFINITE); //ORIGINAL Signaled := WaitForSingleObject(AsyncPtr^.Overlapped.hEvent, 5000); {$ifdef CPORT_CALLTRACE} OutputDebugString( PChar('TCustomComPort.WaitForAsync Signaled='+IntToStr(Signaled)) ); {$endif} Success := (Signaled = WAIT_OBJECT_0) and (GetOverlappedResult(FHandle, AsyncPtr^.Overlapped, BytesTrans, False)); if not Success then raise EComPortExt.Create(FPort, ErrorCode(AsyncPtr), GetLastError); if not FInputCountNotSupported then begin if (AsyncPtr^.Kind = okRead) and (InputCount = 0) then begin {$ifdef CPORT_CALLTRACE} OutputDebugString('TCustomComPort.WaitForAsync SendSignalToLink'); {$endif} SendSignalToLink(leRx, False) end else if AsyncPtr^.Data <> nil then begin {$ifdef CPORT_CALLTRACE} OutputDebugString('TCustomComPort.WaitForAsync TxNotifyLink'); {$endif} TxNotifyLink( TCPortBytes( AsyncPtr^.Data), AsyncPtr^.Size); end; end; {$ifdef CPORT_CALLTRACE} OutputDebugString( PChar('TCustomComPort.WaitForAsync BytesTrans='+IntToStr(BytesTrans)) ); {$endif} Result := BytesTrans; end; В ней на WaitForSingleObject с параметром INFINITE (именно бесконечность была в оригинальном коде) все благополучно наглухо зависает (основной поток программы со всеми вытекающими) при этом какие-то фоновые потоки, не связанные с портами, продолжают работать. Я изменил время ожидания на 5 секунд. Существенно лучше не стало. Программа перестала зависать, но все последующие попытки чтения из порта заканчивались исключением. Это позволило только добавить код, позволяющий вызвать оператора для перезагрузки. Нет, не программы, компьютера, так как даже после выхода порт остается недоступен в операционной системе (консольная команда mode не отображает его в списке). Вот CALLTRACE для успешного запроса / ответа с моими комментариями Цитата 12:23:14 TCustomComPort.WaitForAsync WaitForSingleObject! 000004C8 12:23:14 TCustomComPort.WaitForAsync Signaled=0 12:23:14 TCustomComPort.WaitForAsync TxNotifyLink 12:23:14 TCustomComPort.WaitForAsync BytesTrans=5 //в порт ушло 5 байт данных 12:23:14 TCustomComPort.DoneAsync 12:23:14 TCustomComPort.Read 7 bytes //прочитать из порта 7 байт 12:23:14 >> Before ReadAsync 12:23:14 >> After ReadAsync 12:23:14 TCustomComPort.WaitForAsync WaitForSingleObject! 000004C8 12:23:14 TCustomComPort.WaitForAsync Signaled=0 12:23:14 TCustomComPort.WaitForAsync SendSignalToLink 12:23:14 TCustomComPort.WaitForAsync BytesTrans=7 12:23:14 >> After WaitForAsync //прочитано 7 байт успешно Вот проблемный запрос: Цитата 12:23:14 TCustomComPort.WaitForAsync WaitForSingleObject! 000004C8 12:23:14 TCustomComPort.WaitForAsync Signaled=0 12:23:14 TCustomComPort.WaitForAsync TxNotifyLink 12:23:14 TCustomComPort.WaitForAsync BytesTrans=11 //в порт пошли 11 байт 12:23:14 TCustomComPort.DoneAsync 12:23:14 TCustomComPort.Read 3 bytes / TCustomComPort.Read //прочитать ответ 4 байта 12:23:14 >> Before ReadAsync 12:23:14 >> After ReadAsync 12:23:14 TCustomComPort.WaitForAsync WaitForSingleObject! 000004C8 //ожидаем //тут мы 5 секунд висим 12:23:19 TCustomComPort.WaitForAsync Signaled=258 //код ТАЙМАУТ Вот как выглядят все последующие команды: Цитата 12:23:19 TCustomComPort.WaitForAsync WaitForSingleObject! 000004C8 12:23:19 TCustomComPort.WaitForAsync Signaled=0 12:23:19 TCustomComPort.WaitForAsync TxNotifyLink 12:23:19 TCustomComPort.WaitForAsync BytesTrans=5 //ушло 5 байт 12:23:19 TCustomComPort.DoneAsync 12:23:19 TCustomComPort.Read 7 bytes //прочитать 7 байт 12:23:19 >> Before ReadAsync 12:23:19 >> After ReadAsync 12:23:19 TCustomComPort.WaitForAsync WaitForSingleObject! 000004C8 //ожидаем 12:23:19 TCustomComPort.WaitForAsync Signaled=0 //сразу возврат с кодом 0 после чего где-то в коде компонента возникает исключение, которое мой код отрабатывает как ошибку Ну и вопросы - кто виноват и что делать? Дело не в длине пакета данных, так как в порт постоянно отправляются команды 5, 11 байт и другой длины. Возможно, это поведение операционной системы либо внешнего USB-RS232 адаптера (девайс качественный, не один год используется). А может это как-то связано с поведением контроллера, с которым ведется коммуникация (но после перезагрузки ПК и запуска программы заново контроллер отвечает нормально, перезагрузка по питанию не производится). Еще один момент который может влиять на вероятность возникновения проблемы (несколько раз в сутки при интенсивном использовании) это то что управление на самом деле производится двумя однотипными устройствами через однотипные контроллеры (разные только номера COM портов) и непосредственно перед зависанием в один и тот же контроллер уходит подряд 7 команд серией (7я - зависающая), хотя обычное поведение это одна команда в один порт, за ней тут же одна в другой порт или серии по несколько команд в каждый порт попеременно. Возможно что серия 7 команд в один порт (записать, прочитать) самая длинная во всем коде программы. Кроме того, длина кабеля до проблемного контроллера примерно в 2 раза больше по прямой (но в пределах допустимого) в то время как кабель до другого устройства имеет незначительно меньшую длину и свернут в колечко рядышком с тороидальным трансформатором. Да, я знаю что так нельзя, но пока это никому не мешало, как раз с этим устройством проблем нет. Но так уж сложилось что серия из 7 команд подряд в контроллер, в процессе работы с которым возникает проблема, посылается намного чаще, чем в другой контроллер, потому сказать зависал бы второй - не могу. Поменять их местами тоже не представляется возможным. Понимаю что проблема достаточно сложная, потому прошу гуру форума подсказать что касается работы TComPort с портом и вызовом WaitForSingleObject - почему может быть такое поведение? |
Сообщ.
#2
,
|
|
|
Найдите где вызывается функция ReadFile/WriteFile(hComPort,...,Overlapped) - там будет callback функция, которую система вызывает по окончании чтения/записи в порт. Эта функция должна установить состояние hEvent по окончании данных в буфере, на котором зависает WaitForSingleObject.
Далее стоит искать там (в callback функции) причину зависания. Скорее всего эта функция будет вызываться из ReadAsync. |
Сообщ.
#3
,
|
|
|
Ок, в Read() вызывается ReadAsync()
// perform asynchronous read operation function TCustomComPort.ReadAsync(var Buffer:TCPortBytes; Count: Integer; var AsyncPtr: PCPortAsync): Integer; var Success: Boolean; BytesTrans: DWORD; lastErr:DWORD; begin if AsyncPtr = nil then raise EComPort.Create(FPort,CError_InvalidAsync); AsyncPtr^.Kind := okRead; Buffer[0] := AnsiChar($FF); // Note the position of the caret (^). It compiles fine but doesn't work without "Buffer^" Success := ReadFile(FHandle, Buffer^, Count, BytesTrans, @AsyncPtr^.Overlapped) or (GetLastError = ERROR_IO_PENDING); if not Success then begin lastErr := GetLastError; if FReadAsyncExceptionsEnabled then begin raise EComPortExt.Create(FPort, CError_ReadFailed, lastErr); end else begin Inc(FReadAsyncErrorCount); FReadAsyncLastError := lastErr; result := 0; exit; end; end; Result := BytesTrans; end; То есть если здесь not Success то следует анализировать GetLastError, я Вас правильно понял? Если внутри ReadAsync происходит ошибка (и функция возвращает 0) то какие мои дальнейшие действия - в коде не должна вызываться функция WaitForAsync(), в которой было бесконечное ожидание? |
Сообщ.
#4
,
|
|
|
Не совсем. При возникновении ошибки hEvent не устанавливается в состояние Signaled и система его не установит тоже никогда. Отсюда и будет зависание.
Т.е. При Success = False либо надо вручную вызвать SetEvent или не вызывать функцию Result := WaitForAsync(AsyncPtr); ReadAsync(Buffer, Count, AsyncPtr); Логика должна быть такая. Но тут вызывается функция ожидания даже если при чтении из Com-порта произошла ошибка. Прочитайте про работу hEvent в структуре Overlapped Добавлено т.е. код стоит изменить на такой function TCustomComPort.Read(Buffer:TCPortBytes; Count: Integer): Integer; var AsyncPtr: PCPortAsync; Success: Integer; begin Result := 0; {$ifdef CPORT_CALLTRACE} OutputDebugString('TCustomComPort.Read '+inttostr(Count)+' bytes'); {$endif} CportInitAsync(AsyncPtr); try {$ifdef CPORT_CALLTRACE} OutputDebugString('>> Before ReadAsync'); {$endif} Success := ReadAsync(Buffer, Count, AsyncPtr); // Сохраняем передаваемое количество байт {$ifdef CPORT_CALLTRACE} OutputDebugString('>> After ReadAsync'); {$endif} if Success <> 0 then // Передаваемое количество байт не равно 0, тогда ожидаем завершения передачи Result := WaitForAsync(AsyncPtr); {$ifdef CPORT_CALLTRACE} OutputDebugString('>> After WaitForAsync'); {$endif} finally CportDoneAsync(AsyncPtr); end; end; function TCustomComPort.ReadAsync(var Buffer:TCPortBytes; Count: Integer; var AsyncPtr: PCPortAsync): Integer; var Success: Boolean; BytesTrans: DWORD; lastErr:DWORD; begin if AsyncPtr = nil then raise EComPort.Create(FPort,CError_InvalidAsync); AsyncPtr^.Kind := okRead; Buffer[0] := AnsiChar($FF); // Note the position of the caret (^). It compiles fine but doesn't work without "Buffer^" Success := ReadFile(FHandle, Buffer^, Count, BytesTrans, @AsyncPtr^.Overlapped) or (GetLastError = ERROR_IO_PENDING); if BytesTrans = 0 then BytesTrans := Count; // Если не будет ошибок возвращаем Count; if not Success then begin BytesTrans := 0; // При ошибке возвращаем 0! lastErr := GetLastError; if FReadAsyncExceptionsEnabled then begin raise EComPortExt.Create(FPort, CError_ReadFailed, lastErr); end else begin Inc(FReadAsyncErrorCount); FReadAsyncLastError := lastErr; result := 0; exit; end; end; Result := BytesTrans; end; |
Сообщ.
#5
,
|
|
|
Вариант с вызовом WaitForAsync только если в ReadAsync не было ошибок пока не сработал, при инициализации первые несколько команд прошли, следующая крешанула программу. Буду разбираться днем в чем отличие.
Удалось воспроизвести ошибку, приводившую к зависанию пока в предыдущем варианте кода, но с логом GetLastError и результата Success. Как ни странно, Цитата TCustomComPort.ReadAsync 0 bytes Success, lastErr = 997 То есть BytesTrans равен 0, в MSDN написано Цитата То есть ноль это законно для lpOverlapped <> nil, причем написано даже что этот параметр тоже должен быть nil. А 997 это ERROR_IO_PENDING. То есть в ReadAsync все выполняется как задумано, какого ж тогда лешего происходит таймаут в WaitForSingleObject...ReadFile sets this value to zero before doing any work or error checking. Use NULL for this parameter if this is an asynchronous operation to avoid potentially erroneous results. Уточню. Программа шлет контроллеру пакет-запрос и получает пакет-ответ. Может ли подобная ситуация произойти если контроллер не ответил на запрос, на который согласно документации должен был ответить? Или тогда вернется 0 байт и зависаний быть не должно? Вообще в компоненте TComPort таймауты установлены, 2 секунды или что-то около того, потому если кабели не подключены программа ошибку выводит, а не зависает. |
Сообщ.
#6
,
|
|
|
Цитата Виталь @ Удалось воспроизвести ошибку, приводившую к зависанию пока в предыдущем варианте кода, но с логом GetLastError и результата Success. При получении Success можете просто вызвать SetEvent(AsyncPtr^.Overlapped.hEvent); Signaled := WaitForSingleObject(AsyncPtr^.Overlapped.hEvent, INFINITE); Цитата Виталь @ Может ли подобная ситуация произойти если контроллер не ответил на запрос, на который согласно документации должен был ответить? Может. Цитата Виталь @ Или тогда вернется 0 байт и зависаний быть не должно? Нет. По вашему алгоритму вы до посинения (или в течении 5 сек) ждете передачи от контроллера. Цитата Виталь @ Вообще в компоненте TComPort таймауты установлены, 2 секунды или что-то около того, потому если кабели не подключены программа ошибку выводит, а не зависает. Это другие таймауты. В вашей функции таймаут - ждать до посинения (INFINITE) был в оригинале. |
Сообщ.
#7
,
|
|
|
Спасибо что находите время отвечать среди ночи. Сейчас попробую SetEvent
За это время обернул ReadAsync() и WaitForAsync() в общий try...except и на выходе получил Цитата Да, эта гадость сегодня слишком часто воспроизводится.Exception in Read: COM Port Error: Unknown Error #0 on COM3 Вот этот код 0 передается в параметре AWinCode в EComPortExt.Create() inherited Create( 'COM Port Error: '+ComErrorMessage(AWinCode)+' on '+port); Но это очевидно лезет отсюда Success := (Signaled = WAIT_OBJECT_0) and (GetOverlappedResult(FHandle, AsyncPtr^.Overlapped, BytesTrans, False)); if not Success then raise EComPortExt.Create(FPort, ErrorCode(AsyncPtr), GetLastError); И толком мне ничего не дает. Поскольку COM порт снова пропал из системы - перезагрузка, за это время допишу SetEvent. Почему порт пропадает я так и не понял, думал что как-то связано с тем что не вызывается CportDoneAsync(AsyncPtr), но не то. С SetEvent падает сразу, это в if Success then Цитата 22:32:53 TCustomComPort.Read 5 bytes 22:32:53 TCustomComPort.ReadAsync 0 bytes Success 22:32:53 TCustomComPort.WaitForAsync WaitForSingleObject 000004D0 22:32:53 TCustomComPort.WaitForAsync Signaled=0 22:32:53 >> Exception in Read: COM Port Error: Unknown Error #996 on COM16 (ERROR_IO_INCOMPLETE, Overlapped I/O event is not in a signaled state.) |
Сообщ.
#8
,
|
|
|
При получении этой ошибки добавьте CancelIo(FHandle);
Должно отменить ошибочную операцию в/в. Это должно оживлять COM порт без необходимости перезагрузки. if not Success then begin CancelIo(FHandle); raise EComPortExt.Create(FPort, ErrorCode(AsyncPtr), GetLastError); end; function TCustomComPort.ReadAsync(var Buffer:TCPortBytes; Count: Integer; var AsyncPtr: PCPortAsync): Integer; var Success: Boolean; BytesTrans: DWORD; lastErr:DWORD; begin if AsyncPtr = nil then raise EComPort.Create(FPort,CError_InvalidAsync); AsyncPtr^.Kind := okRead; Buffer[0] := AnsiChar($FF); // Note the position of the caret (^). It compiles fine but doesn't work without "Buffer^" Success := ReadFile(FHandle, Buffer^, Count, BytesTrans, @AsyncPtr^.Overlapped) or (GetLastError = ERROR_IO_PENDING); Result := Count; if not Success then begin lastErr := GetLastError; if lastErr = SUCCESS then begin SetEvent(FPort); exit; end else begin result := 0; if FReadAsyncExceptionsEnabled then begin raise EComPortExt.Create(FPort, CError_ReadFailed, lastErr); end else begin Inc(FReadAsyncErrorCount); FReadAsyncLastError := lastErr; exit; end; end; end; end; Добавлено В общем как-то так должно сработать. Смысл в том, что при успешном начале чтения из COM или полностью считанном блоке из COM порта функция возвращает значение отличное от 0. Тогда должна быть задействована задержка. Хотя судя по всему при Success не стоит запускать задержку. Тогда стоит разделить значения переменной Result на 2. Флаг необходимости задержки и количество считанных байт. Еще можно проверять GetLastError = ERROR_IO_PENDING перед вызовом WaitForAsync. function TCustomComPort.Read(Buffer:TCPortBytes; Count: Integer): Integer; var AsyncPtr: PCPortAsync; Success: Integer; begin Result := 0; {$ifdef CPORT_CALLTRACE} OutputDebugString('TCustomComPort.Read '+inttostr(Count)+' bytes'); {$endif} CportInitAsync(AsyncPtr); try {$ifdef CPORT_CALLTRACE} OutputDebugString('>> Before ReadAsync'); {$endif} Success := ReadAsync(Buffer, Count, AsyncPtr); // Сохраняем передаваемое количество байт {$ifdef CPORT_CALLTRACE} OutputDebugString('>> After ReadAsync'); {$endif} if GetLastError = ERROR_IO_PENDING then // Требуется ожидание завершения асинхронной операции Result := WaitForAsync(AsyncPtr) else Result := Success; {$ifdef CPORT_CALLTRACE} OutputDebugString('>> After WaitForAsync'); {$endif} finally CportDoneAsync(AsyncPtr); end; end; |
Сообщ.
#9
,
|
|
|
Сложно. Схему работы я понимаю так:
ReadAsync вызывает ReadFile с Overlapped, при этом в отличие от традиционного синхронного чтения из файла в буффер, в этот момент ничего не читается, BytesTrans возвращается 0, в Buffer^ ничего не пишется, и в моем случае Success всегда true (даже когда виснет). Далее вызывается WaitForAsync, в ней WaitForSingleObject() возвращает результат как только получен ответ от устройства, GetOverlappedResult собственно считывает данные, ну и далее по тексту. Если я вызову SetEvent где-то раньше WaitForSingleObject() - вызов WaitForSingleObject() вернется сразу, а GetOverlappedResult сразу вернет какую-то ошибку из-за чего появится Exception что я и увидел при прошлом эксперименте. Похожая ситуация если WaitForSingleObject 5 секунд возвращается с ошибкой таймаут. Оно то будет правильно, но блок if not Success then у меня никогда еще не выполнялся и GetLastError у меня всегда ERROR_IO_PENDING (не видел SUCCESS). CancelIO я закинул в Except...end. На удивление, пока не висло (но начинал с того что это процесс спонтанный, вообще счастье что ошибка происходила больше 2 раз за последний час. Очень сложно такие вещи дебажить. Цитата if GetLastError = ERROR_IO_PENDING then // Требуется ожидание завершения асинхронной операции Result := WaitForAsync(AsyncPtr) else Result := Success; вот это сейчас сделаю, это правильно, а то предыдущий вариант if Success <> 0 then все обваливал. |
Сообщ.
#10
,
|
|
|
Цитата Виталь @ ReadAsync вызывает ReadFile с Overlapped, при этом в отличие от традиционного синхронного чтения из файла в буффер, в этот момент ничего не читается, BytesTrans возвращается 0, в Buffer^ ничего не пишется, и в моем случае Success всегда true (даже когда виснет). Далее вызывается WaitForAsync, в ней WaitForSingleObject() возвращает результат как только получен ответ от устройства, GetOverlappedResult собственно считывает данные, ну и далее по тексту. Все так. Только надо еще учесть, что у устройства есть свой внутренний буфер приема. И если в этом буфере уже достаточно байт, то ReadFile может вернуть True, а GetLastError = SUCCESS. Тогда из буфера устройства требуемое количество байт будет скопировано в буфер сразу и асинхронная операция не понадобится. Вот на этом и виснет. Ждет окончания асинхронной операции, которая даже не начиналась и система соответственно не собирается сигнализировать о ее окончании. Поэтому WaitForSingleObject(..., INFINITE) зависает. Но еще в операциях чтения/записи при возникновении ошибки хорошо было бы добавить CancelIo, чтобы не создавать бесконечных ожиданий на прием или передачу у драйвера устройства. Из-за этого и пропадает устройство. Его драйвер занят такой операцией в момент опроса дерева устройств (поэтому его даже не видно). |
Сообщ.
#11
,
|
|
|
У меня короткие запросы и ответы, я не буду утверждать это со стопроцентной уверенностью, но возможно что пакетов больше 20 байт при управлении этим оборудованием не бывает. Если по какой-то причине в буфере накопятся ответы на несколько запросов - возможно. ОК, я учту Ваше замечание. К сожалению оказалось что компонент TComport далек от совершенства и его надо дорабатывать, хотя никогда раньше с подобными трудностями при работе с ним я не сталкивался.
В любом случае Вы сильно помогли. CancelIo в блоке except...end кажется решил проблему. Я вижу несколько Exception в логе по одному порту и одно в логе по второму за остаток вчерашнего вечера, ни одно из них не привело ни к зависанию, ни к постоянным ошибкам при отправке последующих команд и исчезновению порта из системы, как было раньше. Это уже серьезный прогресс. Необходимо будет провести еще несколько экспериментов после исправлений кода и убедиться что ошибка в одной команде не приводит к каким-либо значимым сбоям при работе оборудования. |
Сообщ.
#12
,
|
|
|
Цитата Виталь @ Далее вызывается WaitForAsync, в ней WaitForSingleObject() возвращает результат как только получен ответ от устройства, GetOverlappedResult собственно считывает данные, ну и далее по тексту. Не совсем так. При инициализации порта указывается несколько тайм-аутов, в том числе на приём. Поэтому Wait... может завершиться совершенно законно с нулевым количеством принятых байт. И эту ситуацию надо отличать от штатной. Кроме того, могут быть приняты не все байты за один Wait. Цитата В ней на WaitForSingleObject с параметром INFINITE (именно бесконечность была в оригинальном коде) все благополучно наглухо зависает Может тайм-ауты не спрограммированы ? Как я подозреваю, смысл использования INFINITE (максимального ожидания) в этой wait - функции в том, чтобы реальный тайм-аут операции ожидания определялся заданными тайм-аутами COMM-порта. (Которые могут меняться от проекта к проекту.) --- Возникающая исключительная ситуация говорит от том, что где-то серьёзная паталогия. При этом компонент TComPort может быть совершенно не виноват. --- Виталь, ты читаешь/пишешь данные из одного потока или из разных ? |