Вверх ↑
Этот топик читают: Гость
Ответов: 4621
Рейтинг: 746
#16: 2015-10-27 15:16:46 ЛС | профиль | цитата
Добавил.
карма: 26

0
Ответов: 655
Рейтинг: 18
#17: 2015-10-27 15:53:49 ЛС | профиль | цитата
А все началось с тестирования UART переходника ))) п.с. спасибо за разъяснения по поводу COMEX и за новый компонент)
карма: 0

0
Ответов: 1925
Рейтинг: 172
#18: 2015-10-27 20:29:15 ЛС | профиль | цитата
nesco писал(а):
COMEX не нужно управление чтением, он будет читать данные по приходу автоматически.

А он сразу все данные считывает, которые выдаёт устройство, или по мере поступления будет несколько событий onRead/onSyncRead? Например, есть устройство выдающее в ответ на запрос достаточно длинную строку (допустим, 1000 байт). Эти 1000 байт придут на onRead/onSyncRead единым блоком или будет несколько onRead/onSyncRead по мере формирования ответа устройством?

Я, конечно, протестирую компонент на работе, но пока интересно узнать теоретические выкладки.
карма: 9
0
Разработчик
Ответов: 26061
Рейтинг: 2120
#19: 2015-10-27 22:08:30 ЛС | профиль | цитата
3042 писал(а):
есть устройство выдающее в ответ на запрос достаточно длинную строку (допустим, 1000 байт). Эти 1000 байт придут на onRead/onSyncRead единым блоком или будет несколько onRead/onSyncRead по мере формирования ответа устройством?

Зависит от буфера драйвера порта. Если данные короче буфера, то весь буфер выдаст на выход, если длиннее, то разобьет на части.
карма: 22

0
Ответов: 1925
Рейтинг: 172
#20: 2015-10-28 19:02:54 ЛС | профиль | цитата
Дело в том, что опрос устройства производится каждую секунду, и в ответ выдаётся набор данных. После каждого опроса я хочу убедиться, что все данные ответа получены (а не часть их), перед тем как делать опрос снова. Так вот как узнать, что данные пришли полностью, и устройство уже не будет выдавать данные в ответ на сделанный запрос? Или такое в принципе невозможно в общем случае?
карма: 9
0
Разработчик
Ответов: 26061
Рейтинг: 2120
#21: 2015-10-28 19:44:04 ЛС | профиль | цитата
В таком случае нужна фиксированная длина пакета данных, либо синхронизирующий символ или последовательность символов
карма: 22

0
Ответов: 203
Рейтинг: 2
#22: 2015-10-28 20:32:09 ЛС | профиль | цитата
Добрый день!
nesco писал(а):
В таком случае нужна фиксированная длина пакета данных, либо синхронизирующий символ или последовательность символов

Да но можно спокойно и без этого. Спасибо за компонент COMEX, много лет использую его, уже на объектах работает пару приложений с ним. Пока вроде служит правдой и верой. Одна беда, не рапортует в процессе обмена что вдруг кто то вынул шнурок USB-Сom
Как писали выше в этой теме я использую вариант если в течении определенного времени нет пакетов считаю что данные пришли все и отправляю их дальше на обработку. Вот пример схемы.
code_36455.txt
карма: 0

0
файлы: 1code_36455.txt [2KB] [504]
Ответов: 4621
Рейтинг: 746
#23: 2017-05-12 17:03:04 ЛС | профиль | цитата
Предлагаю подумать на такую тему. Вот в компоненте COMEX есть такой код:


function THICOMEX.CloseCom;
begin
result := false;
if Assigned(thrd) then
begin
thrd.Terminate; // <<<<<<<<<<<<<<<<<<<<<<<
thrd.WaitFor;
free_and_nil(thrd);
end;
...
end;

function THICOMEX.ExecuteRd;
var
Signaled, BytesTrans, Err: DWORD;
BufferRd: string; // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
FStat: TComStat1;
begin
while not Sender.Terminated do
begin
......................
if (FStat.cbInQue <> 0) then
begin
SetLength(BufferRd, FStat.cbInQue); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
ReadFile(hFile, BufferRd[1], FStat.cbInQue, BytesTrans, @OvrRd);
if GetOverlappedResult(hFile, OvrRd, BytesTrans, True) then
begin
ReadStr := BufferRd + #0;
SetLength(ReadStr, BytesTrans);
_hi_onEvent(_event_onRead, ReadStr);
if Assigned(_event_onSyncRead.Event) then Sender.Synchronize(SyncExecRd);
end;
end;
.....................
end;
PurgeComm(hFile, PURGE_RXCLEAR);
Result := 0;
end;

Когда порт открывается, создаётся параллельный поток, в котором выполняется метод THICOMEX.ExecuteRd(). В процессе работы этот метод выделяет память для строки (SetLength(BufferRd, FStat.cbInQue)). Если этот метод завершит свое исполнение (до последнего end), то выделенную под эту строку память Delphi удалит автоматически. Но когда мы закрываем порт, мы делаем THICOMEX.CloseCom(), в котором выполняется thrd.Terminate(). Этот метод тупо прекращает исполнение метода THICOMEX.ExecuteRd() и он никогда не дойдёт до end. Куда девается память, выделенная под строку? Аналогичное происходит, если Terminate() произошло в момент вызова событий из этого метода (_hi_onEvent(_event_onRead, ReadStr)), только память остаётся занятой от строк, которые были выделены в компонентах, подключенных к данному событию. И не только строк, а, например, если в обрабатывающем компоненте создаётся объект PStream, который должен быть освобожден после выдачи его в поток, но этого не происходит, так как исполнение создавшего его метода прерывается.

Какое решение должно применяться для избежания этой проблемы?

Отвечать смогу в понедельник. А кто в теме - пообсуждайте, пожалуйста.

Редактировалось 2 раз(а), последний 2017-05-12 17:54:28
карма: 26

0
Разработчик
Ответов: 26061
Рейтинг: 2120
#24: 2017-05-12 19:12:37 ЛС | профиль | цитата
Netspirit писал(а):
в котором выполняется thrd.Terminate(). Этот метод тупо прекращает исполнение метода THICOMEX.ExecuteRd() и он никогда не дойдёт до end

Пардонсе, а для чего тогда Sender.Terminated? В коде метода Terminate вроде этот флаг выставляется после подачи команды на уничтожение потока? Надо бы Рихтера глянуть, че он там про это безобразие пишет

procedure TThread.Terminate;
begin
TerminateThread(FHandle,0);
FTerminated := True;
end;
карма: 22

0
Ответов: 9906
Рейтинг: 351
#25: 2017-05-13 15:54:54 ЛС | профиль | цитата
nesco писал(а):
Пардонсе

nesco, не сумлевайся -- Netspirit говорит правду.

Интернет говорит о том, что Terminate надо использовать в самом крайнем случае, когда уже вовсе не до жиру.
А правильная логика (как говорит тот же интернет) состоит в том, что вместо Teminate посылают сигнал.
Т.е. устанавливают Event, специально для этого созданный - в сигнальное состояние.
После чего ожидают перехода Thread-а в сигнальное состояние (и, может быть, только по какому-то большому тайм-ауту - выполняют Terminate).

А для того, чтобы поток перешел в сигнальное состояние (типа завершился) - в самих потоках делают не WaitForSingleObject (как у тебя), а WaitForMultipleObjects.
Одно событие - сегодняшнее, а второе - как раз для завершения (которое и должен послать CloseCom).
Во втором случае - корректно завершаем поток (да хоть бы и exit).

В общем, как-то так ........

Редактировалось 5 раз(а), последний 2017-05-15 09:29:31
карма: 9

0
Разработчик
Ответов: 26061
Рейтинг: 2120
#26: 2017-05-13 16:58:16 ЛС | профиль | цитата
Galkov писал(а):
В общем, как-то так ........

Теперь осталось выяснить, как это все правильно реализовать.
карма: 22

0
Ответов: 9906
Рейтинг: 351
#27: 2017-05-14 15:40:12 ЛС | профиль | цитата
Видишь ли, nesco -- у меня нет сейчас под рукой железа.

Не могу даже проверить, зачем у тебя OvrWr.hEvent создан с ручным сбросом (которого нет в кодах), и в исходно-сигнальном состоянии ...
Кстати, открой глаза -- зачем

Могу лишь свои мысли предыдущего поста изложить в виде кодов. Которые компилируются.
Но -- не более. К сожалению
(файл заменен - см. ниже)

Редактировалось 5 раз(а), последний 2017-05-18 11:58:14
карма: 9

0
Разработчик
Ответов: 26061
Рейтинг: 2120
#28: 2017-05-14 16:37:59 ЛС | профиль | цитата
Galkov писал(а):
Могу лишь свои мысли предыдущего поста изложить в виде кодов. Которые компилируются.

Подождем ответа Netspirit-а
карма: 22

0
Ответов: 4621
Рейтинг: 746
#29: 2017-05-15 12:04:23 ЛС | профиль | цитата
Galkov писал(а):
зачем у тебя OvrWr.hEvent создан с ручным сбросом
Это как раз нормально - его будет сигналить overlapped-операция (WaitCommEvent). Хотя, не так: в данном случае это просто не имеет значения.
Galkov писал(а):
и в исходно-сигнальном состоянии
Тут смысла нет, возможно перед началом операции оно все равно сбрасывается, поэтому работало.



Продолжим разговор.
Поскольку глядя на коды KOL.TThread (кстати, вот они, чтобы далеко не ходить, толькое основное):
KOL.TThread


TThread = class(TObj)
public
destructor Destroy; override;

function Execute: integer; virtual;

procedure Resume;

procedure Suspend;
procedure Terminate;

function WaitFor: Integer;
property Handle: THandle read FHandle;
property Suspended: boolean read FSuspended;
property Terminated: boolean read FTerminated;
property ThreadId: DWORD read FThreadId;
property PriorityClass: Integer read GetPriorityCls write SetPriorityCls;
property ThreadPriority: Integer read GetThrdPriority write SetThrdPriority;
property Data : Pointer read FData write FData;
property OnExecute: TOnThreadExecute read FOnExecute write FOnExecute;
property OnSuspend: TObjectMethod read FOnSuspend write FOnSuspend;
property OnResume: TOnEvent read FOnResume write FOnResume;
procedure Synchronize( Method: TThreadMethod );
procedure SynchronizeEx( Method: TThreadMethodEx; Param: Pointer );
property AutoFree: Boolean read F_AutoFree write F_AutoFree;
end;

destructor TThread.Destroy;
begin
if not FTerminated then
begin
Terminate;
WaitFor;
end;
if (FHandle <> INVALID_HANDLE_VALUE) then
CloseHandle(FHandle);
inherited;
end;

function TThread.Execute: integer;
begin
Result := 0;
if Assigned( FOnExecute ) then
Result := FOnExecute( Self );
if F_AutoFree then
begin
FTerminated := TRUE;
Free;
end;
end;

procedure TThread.Terminate;
begin
TerminateThread(FHandle,0);
FTerminated := True;
end;

function TThread.WaitFor: Integer;
begin
RefInc;
Result := -1;
if FHandle = 0 then Exit;
WaitForSingleObject(FHandle, INFINITE);
GetExitCodeThread(FHandle, DWORD(Result));
RefDec;
end;
единственным способом установить Terminated=True является вызов Terminate, то вот это while not Sender.Terminated do не имеет в данной реализации никакого смысла - когда Terminated станет True этот цикл уже будет грубо прекращен. Особенно печально выглядит ситуация, когда COMEX.doClose будет вызвано из, например, COMEX.onRead. Тогда в вышеприведеной THICOMEX.CloseCom всё, что после thrd.Terminate() вообще не выполнится, в том числе free_and_nil(thrd), free_and_nil(thwr), CloseHandle(hFile).

Отступление.
Не знаю чем руководствовался Кладов, когда добавлял TerminateThread() в TThread.Terminate. Возможно, чтобы название метода соответствовало его назначению. Но в оригинальном Delphi Classes.TThread метод Terminate делает только установку свойства Terminated, что даёт некоторый смысл коду компонента COMEX, особенно учитывая, что nesco при написании этого компонента смотрел примеры как раз, вероятней всего, для Delphi.

Поскольку пользователи не имеют возможности регулярно обновлять у себя компиляторы, то мы избегаем вносить изменения в KOL.pas. Поэтому все дальнейшие обсуждения не будут предполагать правки KOL.TThread, вместо этого последним этапом обсуждения будет выработка альтернативного решения.

--- Добавлено в 2017-05-15 12:08:19

Итак, как выше написал Galkov, первый вывод: Нельзя использовать метод TThread.Terminate(). Просто забудьте о его существовании.

А как же тогда останавливать поток? Первый вариант - добавить в класс THICOMEX поле FStopReading: Boolean. Тогда код может выглядеть так:


function THICOMEX.CloseCom;
begin
result := false;
if Assigned(thrd) then
begin
FStopReading := True; // <<<<<<<<<<<<<<<
thrd.WaitFor;
free_and_nil(thrd);
end;
......................
end;

function THICOMEX.ExecuteRd;
......................
begin
while not FStopReading do // <<<<<<<<<<<<<<<
begin
......................
end;
......................
end;
У этого варианта есть недостатки. Во-первых, такое нужно делать всегда, когда мы используем класс TThread, то есть, лишняя работа. Во-вторых, этих флагов нужно добавить столько, сколько потоков мы используем. Например, в THICOMEX нужно добавить ещё и флаг для остановки потока записи (вместо thwr.Terminate).
Предложение такое: а давайте в наш класс TThread добавим свойство Stopped: Boolean и метод TThread.Stop() который выставляет Stopped = True.
Тогда код будет выглядеть так:


function THICOMEX.CloseCom;
begin
result := false;
if Assigned(thrd) then
begin
thrd.Stop; // <<<<<<<<<<<<<<<
thrd.WaitFor;
free_and_nil(thrd);
end;
......................
end;

function THICOMEX.ExecuteRd;
......................
begin
while not Sender.Stopped do // <<<<<<<<<<<<<<<
begin
......................
end;
......................
end;
Просто и понятно.

--- Добавлено в 2017-05-15 12:09:54

Продолжим дальше. Метод TThread.WaitFor приостанавливает вызваший его поток, пока поток в TThread не завершит свое исполнение, то есть это ожидание завершения параллельного потока. В текущей реализации при thrd.Terminate дальнейший вызов thrd.WaitFor особого смысла не имел - поток и так будет убит. Но, когда мы вместо thrd.Terminate делаем thrd.Stop, поток (метод THICOMEX.ExecuteRd) не убивается мгновенно - он продолжает стоять на таймаутах и выполнять другие операторы до тех пор, пока не дойдёт в начало цикла, где стоит проверка while not Sender.Stopped do.
Если после thrd.Stop вызвать сразу free_and_nil(thrd), то объект thrd (он же Sender) будет освобожден, и тогда параллельный поток упадёт на проверке while not Sender.Stopped do, потому что объекта Sender уже не существует. По крайней мере, так должен размышлять автор кода, и первое, что ему должно придти в голову - это дождаться завершения метода THICOMEX.ExecuteRd и параллельного потока, для чего он и использовал метод WaitFor.
А тепер представьте, что пользователь решил закрыть COM порт и вызвал COMEX.doClose (и следовательно, THICOMEX.CloseCom) из события COMEX.onRead.
Что произойдет в этом случае? А в этом случае параллельный поток в особе метода THICOMEX.ExecuteRd выполнит команду thrd.WaitFor и... и будет вечно ожидать завершения самого себя. Это назвыется deadlock. А если вызвать COMEX.doClose из синхронного метода COMEX.onSyncRead, то повиснет ещё и главный поток приложения.

Отступление.
Почему я написал "По крайней мере, так должен размышлять автор кода"? Потому что Кладов что-то подобное предполагал, и на самом деле метод TObj.Destroy (который вызывается по free_and_nil(thrd)) не уничтожает объект, а уменьшает специальный счетчик, и когда этот счетчик дойдёт до 0, только тогда объект будет уничтожен. А у TThread перед запуском параллельного потока на исполнение счетчик как раз увеличивается. Идея важная и я к ней ещё вернусь. Но в KOL её реализация, по-моему, бажная, и нам KOL-овскую реализацию всё равно использовать неудобно, потому что у нас классы, а не объекты и нет наследования KOL-овских объектов, поэтому всё равно нужно делать свою реализацию.

Как мы видим, метод WaitFor мы не можем использовать, даже если обойдём deadlock (это возможно).
Тогда вопрос: как нам следует освободить объект thrd в методе THICOMEX.CloseCom после метода thrd.Stop?

Редактировалось 13 раз(а), последний 2017-05-15 14:37:26
карма: 26

0
Ответов: 9906
Рейтинг: 351
#30: 2017-05-15 14:06:47 ЛС | профиль | цитата
Netspirit писал(а):
У этого варианта есть недостатки. Во-первых ....... Во-вторых ........

Есть еще и третий недостаток. Он Вами отмечен: он продолжает стоять на таймаутах и ...
Этого вполне достаточно, чтобы похоронить вариант "с флагом" - сколько стоять то будет: секунду, минуту, час, два дня ????

Netspirit, Вы так и не сказали, чем Вас не устраивает предложенный мной вариант (вообще-то, я его где-то вычитал).
Вариант deadlock-а интересен. Теоретически. Можно подумать про NewThreadAutoFree (или его одноименное свойство), а в CloseCom вообще про потоки не вспоминать.
Но защита от ЛЮБОГО дурака - не самое благодарное занятие.
Признаюсь, вызов doOpen/doClose из события onRead (без таймерной развязки) мне в голову не пришел.

Но, по большому-то счету -- не ходи дурак под окном, вот вам и весь сказ

Редактировалось 1 раз(а), последний 2017-05-15 18:15:17
карма: 9

0
Сообщение
...
Прикрепленные файлы
(файлы не залиты)