![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Синхронизация через SendMessage [D2005, XP]
Alex4445 © (2015-12-15 22:37:00)
type
TMyForm = class (TForm)
...
procedure SyncMsg (var T : TMessage); message WM_USER + 1;
end
var
MyForm : TMyForm;
procedure TMyForm.SyncMsg (var T : TMessage);
begin
CSL.Send ('Enter'); // CodeSiteLogger - система логгирования
sleep (2000);
CSL.Send ('Leave');
end;
TMyThread = class (TThread)
procedure Execute; override;
end;
procedure TMyThread.Execute;
begin
while (not Terminated) do
SendMessage (MyForm.Handle,WM_USER+1,0,0);
sleep (100);
end;
end;
Создается несколько однотипных потоков, которые через SendMessage выполняют определенный код (в данному случае лог + слип) в главном потоке.
Ожидаю увидеть в логе:
Enter
Leave
Enter
Leave
...
Enter
Leave
...
Т.е главный поток обрабатывает SendMessage-ы последовательно.
Вижу
Enter
Leave
Enter // ???
Enter // ???
Leave
Enter
Leave
...
Как такое получается? Как начал выполняться последующий SendMesssage пока не завершился предыдущий?
Или это "обман" связанный с работой логгера ?
sniknik (2015-12-18 14:02:00) [1]> переполнения стека быть не должно
не было бы, если бы не Application.ProcessMessages; на нем входит в новый цикл обработки в ней читает следующее сообщение очереди, вызывает функцию и т.д. и т.д. фактически получается рекурсия.
Юрий Зотов (2015-12-18 13:26:00) [2]ИМХО, при PostMessage переполнения стека быть не должно. Доп. потоки только набрасывают сообщения в очередь главного потока, а главный поток эту свою очередь ЛИНЕЙНО разгребает, одно сообщение за другим. То есть, нет вложенности обработки - значит, нет и переполнения стека.
sniknik (2015-12-17 17:35:00) [3]в итоге Rouse в первом же посте был прав. ;)
и кстати PostMessage в этом случае не помогает. ;( а при малых "таймаутах" еще
и стек быстро кончается (обработчики событий один в один вкладываются, как матрешки)
Юрий Зотов (2015-12-17 16:18:00) [4]Рассмотрим 4 первых сообщения.
1. Enter 3904:3600 - доп. поток 3600 через SendMessage вызвал обработчик сообщения (это первый вызов) и главный поток 3904 начал этот обработчик выполнять (но еще не закончил).
2. Enter 3904:6700 - в этот момент доп. поток 6700 через SendMessage тоже вызвал обработчик сообщения (это второй вызов) и главный поток 3904 начал этот обработчик выполнять.
3. Leave 3904:6700 - главный поток обработал ВТОРОЙ вызов.
4. Leave 3904:3600 - главный поток обработал ПЕРВЫЙ вызов. Причем вполне успешно - а это означает, что перед п.2 состояние обработчика сообщения было сохранено, а после п.3 - восстановлено.
===============
То есть, имеем вариант 1 в чистом виде (см. [30], [31], [34]). Что и пытаюсь донести.
sniknik (2015-12-17 15:01:00) [5]!!!!
> разве что в CodeSiteLogger Send есть что-то подобное - Application.ProcessMessages;
Проверил -
procedure TForm1.SyncMsg (var Message : TMessage);
begin
Memo1.Lines.Add('Enter '+IntTostr(GetCurrentThreadId)+':'+IntTostr(Message.WParam));
Application.ProcessMessages;
Sleep(100);
Memo1.Lines.Add('Leave '+IntTostr(GetCurrentThreadId)+':'+IntTostr(Message.WParam));
Application.ProcessMessages;
end;
результат (кусочек, и так очевидно, ну и плюс "жёсткий зависон" исчез(само собой ;), ТС можно по нему ориентироваться, если его код не вешает основной поток то что-то подобное там есть...) -
Enter 3904:3600
Enter 3904:6700
Leave 3904:6700
Leave 3904:3600
Enter 3904:6700
Leave 3904:6700
Leave 3904:6200
Enter 3904:6700
Enter 3904:3600
Leave 3904:3600
Leave 3904:6700
Enter 3904:3600
Leave 3904:3600
Leave 3904:2636
Enter 3904:3936
Enter 3904:6200
Enter 3904:4356
Enter 3904:6700
Enter 3904:3600
sniknik (2015-12-17 14:53:00) [6]> хотя "как???" тогда непонятно
разве что в CodeSiteLogger Send есть что-то подобное - Application.ProcessMessages;
sniknik (2015-12-17 14:44:00) [7]> либо с Вашим утверждением они не стыкуются
либо ТС вбросил "дезу", у него приведенный код не рабочий, т.е. можно предположить, что "здесь пример,а на самом деле код другой".
[0]
procedure TMyThread.Execute;
begin
while (not Terminated) do
SendMessage (MyForm.Handle,WM_USER+1,0,0);
sleep (100);
end;
end;
либо реально
Rouse_ © (16.12.15 00:03) [1]
Скорее логер, синхронизация на первый взгляд валидная
хотя "как???" тогда непонятно
сделал пример
procedure TMyThread.Execute;
begin
while (not Terminated) and (not MassStop) do begin
SendMessage(Form1.Handle, WM_USER+1, GetCurrentThreadId, 0);
Sleep(Random(100));
end;
end;
procedure TForm1.SyncMsg (var Message : TMessage);
begin
Memo1.Lines.Add('Enter '+IntTostr(GetCurrentThreadId)+':'+IntTostr(Message.WParam));
Sleep(100);
Memo1.Lines.Add('Leave '+IntTostr(GetCurrentThreadId)+':'+IntTostr(Message.WParam));
end;
правильная последовательность, реальные пары (Message.WParam id потока) ничего не смешивается... хотя добавил Random и поставил меньше ожидание ускорить процесс "на ошибку".
в общем единственное, что осталось против подобного подхода это
> все потоки будут работать последовательно как один, взаимно ожидая друг друга
даже критическая секция для этого не нужна оказалось.
и выйти никак нельзя, клики/кнопки/клавиши все блокирует.
Юрий Зотов (2015-12-17 13:21:00) [8]> han_malign © (17.12.15 08:43) [34]
> именно вариант 2 является единственным верным...
Тогда откуда взялся сабж? Цитирую:
> Enter
> Leave
> Enter // ???
> Enter // ???
> Leave
> Enter
> Leave
> ...
> Как такое получается? Как начал выполняться последующий
> SendMesssage пока не завершился предыдущий?
Напоминаю, что SendMesssage и обработчик сообщения оба раза выполняются в главном потоке. И он вполне успешно сохраняет текущее состояние первого вызова в момент второго. А после завершения второго так же успешно это состояние восстанавливает.
Либо я неправильно трактую эти факты, либо с Вашим утверждением они не стыкуются. Прошу ответить на вопрос, который я выделил жирным подчеркнутым.
Alex4445 (2015-12-17 12:50:00) [9]
> han_malign © (17.12.15 08:43) [34]
> - и тем не менее именно вариант 2 является единственным
> верным...
Согласен,
косвенно нашел этому подтверждение в мануале
"Resources can also be protected using critical sections or the multi-read exclusive-write synchronizer as an alternative to calling Synchronize."
Спасибо всем за участие в обсуждении!
Игорь Шевченко (2015-12-17 10:30:00) [10]
> А если вызывать его из внешнего процесса?
А если Рихтера почитать ? Поуважать труд популяризаторов.
DayGaykin (2015-12-17 10:28:00) [11]SendMessage выполняется в потоке, который его вызывал? Вот это открытие.
А если вызывать его из внешнего процесса?
han_malign (2015-12-17 08:43:00) [12]
> Вариант 1 мне кажется более реальным
- и тем не менее именно вариант 2является единственным верным...
Последовательность выполнения гарантируется Intel и Microsoft...
У одного потока только один контекст и один стек - выполнить, что то другое (сохранив его состояние)- можно только в контексте другого потока, но не того же самого...
Реентерабельность SendMessage - работает, но только в контексте основного потока - и это честная рекурсия, ни о каком прерывании выполения речи не идёт...
Если бы был возможен Вариант 1- то никакие примитивы синхронизации не работали бы...
Есть конечно вариант с явным setjmp/longjmp - но это не тот случай...
Поэтому сабжскорее всего - как раз в системе логирования(видимо из-за излишне умной буфферизации)... Если кто не заметил - в сабжеи парность Enter/Leave - не соблюдена...
З.Ы. Я на этом уже не один deadlock словил... Кстати SendMessage - не единственная засада, последний раз я на Visible(aka SetWindowPos, из доп. потока) на сериализацию в основной поток напоролся, который ждал критическую секцию захваченную этим доп. потоком...
З.З.Ы. И не стоит путать TControl.Perform() и SendMessage()...
NoUser (2015-12-17 00:58:00) [13]Упс, насчет герц пардон,
а ответ на вопрос "А и Б сидели на трубе ..." - это Ваш CSL !
NoUser (2015-12-17 00:45:00) [14]> Alex4445 (15.12.15 22:37) [0]
В чем смысл задавать задания с частотой 10Гц, если они будут выполняться с частотой 0,5Гц ?
- проверить есть ли порог для очереди сообщений?
-- да, он есть, что не влезет - будет отброшено.
> Синхронизация через SendMessageвозможна, если атомарное действие будет выполняться внутри обработчика сообщений в рамках обработки одного сообщения.
Юрий Зотов (2015-12-16 18:48:00) [15]> Вариант 1 мне кажется более реальным
Собственно, если бы был реальным вариант 2, то не было бы и сабжа.
Юрий Зотов (2015-12-16 18:40:00) [16]> han_malign © (16.12.15 17:42) [28]
>в D4 - Synchronize работал именно через SendMessage...
Верно. Чем обеспечивалась синхронизация с главным потоком приложения. Но не между несколькими доп. потоками. А сабж именно о них
1. Поток 1 в cвоем Execute вызвал метод M главной формы через Synchronize. Этот метод (для удобства обозначим его M1) начинает выполняться в главном потоке, но до конца еще не выполнился.
2. В этот момент поток 2 в cвоем Execute вызвал тот же самый метод M той же главной формы, и тоже через Synchronize. Этот метод (для удобства обозначим его M2) тоже будет выполнен в главном потоке
Но когда он будет выполнен?
Вариант 1: главный поток временно прервет выполнение метода М1, (сохранив его состояние), затем выполнит метод M2, затем вернется к выполнению метода M1 (напомню, что M1 и M2 - это один и тот же метод главной формы, мы лишь для удобства обозначаем его еще и цифрами).
Вариант 2: главный поток сначала закончит выполнение метода M1, а затем выполнит метод M2.
Вариант 1 мне кажется более реальным (потому что по факту он аналогичен вызову подпрограммы M2 из подпрограммы M1 - то есть, механизм срабатывает автоматически).
sniknik (2015-12-16 17:57:00) [17]> если нам безразличен порядок записей в лог (см. [15]).
как раз таки наоборот... порядок будет от порядка очереди сформированной "постом", та же все события с одним приоритетом будут, без очереди не обработаются.
han_malign (2015-12-16 17:42:00) [18]
> Так мне кажется, по логике, что 4 не наступит пока не закончится 2.
- правильно кажется...
Если кто забыл, в D4 - Synchronize работал именно через SendMessage...
А в Windows 3.X переключение на другую задачупроисходило в GetMessage, и что то мне подсказывает, что сериализация SendMessage именно там до сих пор и происходит...
Юрий Зотов (2015-12-16 17:24:00) [19]> sniknik © (16.12.15 15:51) [25]
> нафиг критическую секцию.... ИМХО. PostMessage рулит! :)
Согласен, если нам безразличен порядок записей в лог (см. [15]).
> Alex4445 (16.12.15 15:54) [26]
> нить обработки сообщений не возьмет следующее сообщение
> пока не закончит первое.
Почему бы и нет? Это просто повторный вызов одного и того же метода (так наз. реентерабельность). Похоже на рекурсию, но с той разницей, что метод вызывается не из себя же, а извне.
Alex4445 (2015-12-16 15:54:00) [20]
> 1. Поток 1 посылает сообщение 1 и блокируется системой.
> 2. Главный поток начал (но еще не закончил) обработку сообщения
> 3. Поток 2 посылает сообщение 2 и блокируется системой.
> 4. Главный поток обработал сообщение 2.
Так мне кажется, по логике, что 4 не наступит пока не закончится 2.
Потому что нить обработки сообщений не возьмет следующее сообщение пока не закончит первое.