Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Синхронизация через SendMessage [D2005, XP]


Страницы:   1 2 3

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-16 15:51:00) [21]

> то нужна критическая секция.
одно блокирует, другое (sleep) приостанавливает текущий и разблокирует другие... в итоге или все потоки будут работать последовательно как один, взаимно ожидая друг друга, либо, в худшем случае можно нарваться на взаимоблокировку.

нафиг критическую секцию.... ИМХО. PostMessage рулит! :)


Юрий Зотов   (2015-12-16 15:15:00) [22]

Блин. Начал забывать WinAPI. Но смутно вспомнил, что в описании SendMessage что-то на эту тему говорилось. Решил посмотреть - и вот что увидел:

If the specified window was created by the calling thread, the window procedure is called immediately as a subroutine. If the specified window was created by a different thread, the system switches to that threadand calls the appropriate window procedure.Messages sent between threads are processed only when the receiving thread executes message retrieval code. The sending thread is blockeduntil the receiving thread processes the message.

То есть, Вы правы - обработчик сообщения выполняется в главном потоке. Но это мало что меняет, потому что сценарий может быть таким:

1. Поток 1 посылает сообщение 1 и блокируется системой.
2. Главный поток начал (но еще не закончил) обработку сообщения 1.
3. Поток 2 посылает сообщение 2 и блокируется системой.
4. Главный поток обработал сообщение 2.
5. Поток 2 разблокировался системой и пошел работать дальше.
6. Главный поток закончил обработку сообщения 1.
7. Поток 1 разблокировался системой и пошел работать дальше.

В логе увидим:

Enter поток 1
Enter поток 2
Leave поток 2
Leave поток 1

Значит, если мы хотим видеть "Leave поток N" сразу после "Enter поток N", то нужна критическая секция.


Alex4445   (2015-12-16 15:02:00) [23]

Та же вариация самого первого поста с OutPutDebugString  


unit Unit1;

interface

uses
 Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
 Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
 TMyForm = class(TForm)
   btn1: TButton;
   procedure btn1Click(Sender: TObject);
 private
   { Private declarations }
 public
   { Public declarations }
   procedure SyncMsg (var T: TMessage); message WM_USER + 1;
 end;

 TThread1  = class  (TThread)
   procedure Execute; override;
 end;

var
 MyForm: TMyForm;

implementation
{$R *.dfm}

{ TThread1 }

procedure TThread1.Execute;
begin
 FreeOnTerminate := True;
// OutPutDebugString (PCHar('Enter Thread Execute Thread  ' + IntToStr(GetCurrentThreadID)));
  SendMessage(MyForm.Handle, WM_USER+1,0,0);
//  OutPutDebugString (PCHar('Done  Thread Execute Thread  ' + IntToStr(GetCurrentThreadID)));
end;

{ TMyForm }

procedure TMyForm.btn1Click(Sender: TObject);
var
A : Integer;
begin
 OutPutDebugString (PChar('Button Clicked Thread  ' + IntToStr(GetCurrentThreadID)));
 for A := 0 to 10 do
   TThread1.Create;
end;

procedure TMyForm.SyncMsg(var T: TMessage);
begin
 OutPutDebugString (PCHar('Enter Message Thread  ' + IntToStr(GetCurrentThreadID)));
 sleep (2555);
 OutPutDebugString (PChar('Exit Message Thread  ' + IntToStr(GetCurrentThreadID)));
end;

end.


В логе уже отображает ожидаемый изначально результат: последовательный
Enter Message
Exit  Message
...


Debug Output: Button Clicked Thread  3420 Process Project1.exe (8140)
Thread Start: Thread ID: 9960. Process Project1.exe (8140)
Thread Start: Thread ID: 10132. Process Project1.exe (8140)
Thread Start: Thread ID: 8228. Process Project1.exe (8140)
Thread Start: Thread ID: 5324. Process Project1.exe (8140)
Thread Start: Thread ID: 6312. Process Project1.exe (8140)
Thread Start: Thread ID: 9732. Process Project1.exe (8140)
Thread Start: Thread ID: 7696. Process Project1.exe (8140)
Thread Start: Thread ID: 5240. Process Project1.exe (8140)
Thread Start: Thread ID: 9692. Process Project1.exe (8140)
Thread Start: Thread ID: 8916. Process Project1.exe (8140)
Thread Start: Thread ID: 9544. Process Project1.exe (8140)
Debug Output: Enter Message Thread  3420 Process Project1.exe (8140)
Debug Output: Exit Message Thread  3420 Process Project1.exe (8140)
Debug Output: Enter Message Thread  3420 Process Project1.exe (8140)
Thread Exit: Thread ID: 5240. Process Project1.exe (8140)
Debug Output: Exit Message Thread  3420 Process Project1.exe (8140)
Debug Output: Enter Message Thread  3420 Process Project1.exe (8140)
Thread Exit: Thread ID: 10132. Process Project1.exe (8140)
Debug Output: Exit Message Thread  3420 Process Project1.exe (8140)
Debug Output: Enter Message Thread  3420 Process Project1.exe (8140)
Thread Exit: Thread ID: 9960. Process Project1.exe (8140)


Alex4445   (2015-12-16 15:02:00) [24]

Та же вариация самого первого поста с OutPutDebugString  


unit Unit1;

interface

uses
 Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
 Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
 TMyForm = class(TForm)
   btn1: TButton;
   procedure btn1Click(Sender: TObject);
 private
   { Private declarations }
 public
   { Public declarations }
   procedure SyncMsg (var T: TMessage); message WM_USER + 1;
 end;

 TThread1  = class  (TThread)
   procedure Execute; override;
 end;

var
 MyForm: TMyForm;

implementation
{$R *.dfm}

{ TThread1 }

procedure TThread1.Execute;
begin
 FreeOnTerminate := True;
// OutPutDebugString (PCHar('Enter Thread Execute Thread  ' + IntToStr(GetCurrentThreadID)));
  SendMessage(MyForm.Handle, WM_USER+1,0,0);
//  OutPutDebugString (PCHar('Done  Thread Execute Thread  ' + IntToStr(GetCurrentThreadID)));
end;

{ TMyForm }

procedure TMyForm.btn1Click(Sender: TObject);
var
A : Integer;
begin
 OutPutDebugString (PChar('Button Clicked Thread  ' + IntToStr(GetCurrentThreadID)));
 for A := 0 to 10 do
   TThread1.Create;
end;

procedure TMyForm.SyncMsg(var T: TMessage);
begin
 OutPutDebugString (PCHar('Enter Message Thread  ' + IntToStr(GetCurrentThreadID)));
 sleep (2555);
 OutPutDebugString (PChar('Exit Message Thread  ' + IntToStr(GetCurrentThreadID)));
end;

end.


В логе уже отображает ожидаемый изначально результат: последовательный
Enter Message
Exit  Message
...


Debug Output: Button Clicked Thread  3420 Process Project1.exe (8140)
Thread Start: Thread ID: 9960. Process Project1.exe (8140)
Thread Start: Thread ID: 10132. Process Project1.exe (8140)
Thread Start: Thread ID: 8228. Process Project1.exe (8140)
Thread Start: Thread ID: 5324. Process Project1.exe (8140)
Thread Start: Thread ID: 6312. Process Project1.exe (8140)
Thread Start: Thread ID: 9732. Process Project1.exe (8140)
Thread Start: Thread ID: 7696. Process Project1.exe (8140)
Thread Start: Thread ID: 5240. Process Project1.exe (8140)
Thread Start: Thread ID: 9692. Process Project1.exe (8140)
Thread Start: Thread ID: 8916. Process Project1.exe (8140)
Thread Start: Thread ID: 9544. Process Project1.exe (8140)
Debug Output: Enter Message Thread  3420 Process Project1.exe (8140)
Debug Output: Exit Message Thread  3420 Process Project1.exe (8140)
Debug Output: Enter Message Thread  3420 Process Project1.exe (8140)
Thread Exit: Thread ID: 5240. Process Project1.exe (8140)
Debug Output: Exit Message Thread  3420 Process Project1.exe (8140)
Debug Output: Enter Message Thread  3420 Process Project1.exe (8140)
Thread Exit: Thread ID: 10132. Process Project1.exe (8140)
Debug Output: Exit Message Thread  3420 Process Project1.exe (8140)
Debug Output: Enter Message Thread  3420 Process Project1.exe (8140)
Thread Exit: Thread ID: 9960. Process Project1.exe (8140)


Alex4445   (2015-12-16 14:59:00) [25]

Это добавляет сам CodeSiteLogger в обработчике Send ();
Собственно, для чистоты эксперимента
CodeSite.Send ()  заменен на OutPutDebugString

utPutDebugString (PChar('Enter Message Thread  ' + IntToStr(GetCurrentThreadID)));

Результат:

Debug Output: Button Clicked Thread  5256 Process Project1.exe (1116)
Thread Start: Thread ID: 10136. Process Project1.exe (1116)
Debug Output: Enter Thread Execute Thread  10136 Process Project1.exe (1116)
Debug Output: Enter Message Thread  5256 Process Project1.exe (1116)
Debug Output: Exit Message Thread  5256 Process Project1.exe (1116)
Debug Output: Done  Thread Execute Thread  10136 Process Project1.exe (1116)
Thread Exit: Thread ID: 10136. Process Project1.exe (1116)


Юрий Зотов   (2015-12-16 14:48:00) [26]

Откуда взялись выделенные части лога? В коде их нет.

Button Clicked              Thread 1736 1736
Enter Thread Execute    Thread 7656
Enter Message              Thread 1736
Exit Message                Thread 1736
Done  Thread Execute   Thread 7656
/CODE>


Alex4445   (2015-12-16 14:38:00) [27]

Народ, вопрос никакого отношения к логгированию не имеет.
Для ведения логов используется CodeSiteLogger, входящий в комплект Delphi XE.
Логи были добавлены в обработчик SendMessage, чтобы понять выполняются ли вызовы последовательно один за другим.


unit Unit1;

interface

uses
 Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
 Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
 TMyForm = class(TForm)
   btn1: TButton;
   procedure btn1Click(Sender: TObject);
 private
   { Private declarations }
 public
   { Public declarations }
   procedure SyncMsg (var T: TMessage); message WM_USER + 1;
 end;

 TThread1  = class  (TThread)
   procedure Execute; override;
 end;

var
 MyForm: TMyForm;

implementation
USes CodeSiteLogging;
{$R *.dfm}

{ TThread1 }

procedure TThread1.Execute;
begin
 FreeOnTerminate := True;
 CodeSite.Send('Enter Thread Execute ', GetCurrentThreadId);
  SendMessage(MyForm.Handle, WM_USER+1,0,0);
 CodeSite.Send('Done  Thread Execute');
end;

{ TMyForm }

procedure TMyForm.btn1Click(Sender: TObject);
begin
 CodeSite.Send('Button Clicked');
 TThread1.Create;
end;

procedure TMyForm.SyncMsg(var T: TMessage);
begin
 CodeSite.Send('Enter Message');
 sleep (2000);
 CodeSite.Send('Exit Message');
end;

end.


Сообщения логгера

Button Clicked              Thread 1736
Enter Thread Execute     Thread 7656
Enter Message              Thread 1736
Exit Message                 Thread 1736
Done  Thread Execute     Thread 7656


Юрий Зотов   (2015-12-16 14:12:00) [28]

А если уж очень сильно хочется синхронизации, то выбросить SendMessage, выбросить все эти Sleep'ы, выбросить критическую секцию, выбросить даже и само сообщение. И оставить простейший вариант: писать в лог через Synchronize.

Только тогда пропадает весь смысл второго потока. Поэтому и его надо будет выбрасывать. И останется обычное однопоточное приложение.
:o)


sniknik   (2015-12-16 13:45:00) [29]

> код выполняется в потоке Thread/Main
сохрани в лог значение GetCurrentThreadId и посмотри.


Юрий Зотов   (2015-12-16 13:44:00) [30]

Весь метод TMyForm.SyncMsg будет выполнен в потоке Thread1. С чего бы ему выполняться в потоке Main? То, что этот метод написан в классе формы - неважно.

Thread1 выполняет цепочку вызовов:
- TMyThread.Execute
- SendMessage
- TMyForm.SyncMsg (напрямую вызван из SendMessage)
- sleep (100


А поток Main в этой цепочке вообще не участвует. Он ждет сообщения в СВОЕЙ очереди.


Юрий Зотов   (2015-12-16 13:35:00) [31]

А вообще, если речь идет всего лишь о логировании, то я согласен, что надо использовать PostMessage и не париться. Кстати, и лог получится более информативным (особенно, если потоку дать какое-нибудь имя и выводить это имя в лог вместе с другой инфой).


Alex4445   (2015-12-16 13:31:00) [32]


> выполнен этот код будет все равно в потоке, вызвавшем
> SendMessage.


Юрий, прошу пояснения. ...


procedure TMyThread.Execute;
begin
  while (not Terminated) do
      ...     --- код выполняется в потоке Thread1
      SendMessage (....);  --- вызов выполняется в потоке Thread1
      sleep (100);     ...     --- код выполняется в потоке Thread1
  end;
end;

procedure TMyForm.SyncMsg (var T : TMessage);
begin
 CSL.Send ('Enter');   -- код выполняется в потоке Main (главный поток приложения).
 sleep (2000);          -- код выполняется в потоке Main  
 CSL.Send ('Leave');  -- код выполняется в потоке Main
end;


Юрий Зотов   (2015-12-16 13:30:00) [33]

> Я полагал, что основной поток в очереди сообщений берет SendMessage
> и "зависает" на нем пока не выполнит

SendMessage - это прямой (т.е. обычный) вызов обработчика, в очередь она ничего не помещает. Вы вызываете SendMessage из НЕглавного потока - вот в этом НЕглавном потоке вся обработка сообщения и выполняется. И, несмотря на то, что обработчик написан в главной форме, выполняется он все равно в НЕглавном потоке (в том, который вызвал SendMessage).

А главный поток здесь ни при чем, он в это время ждет, когда у НЕГО в очереди появится ЕГО сообщение: классический цикл while GetMessage(...)


Юрий Зотов   (2015-12-16 13:21:00) [34]

Зачем нужна FCS ?


sniknik   (2015-12-16 13:19:00) [35]

> Хмм.. как это "без очереди"?
ну вот так, считай Send просто прямым вызовом функции, и если в одном приложении то аналог -
procedure TMyThread.Execute;
begin
   while (not Terminated) do
       MyForm.SyncMsg(nil); //не буду для примера формировать структуру
       sleep (100);
   end;
end
;
ну без учета vcl/нельзя из потока и т.д.

> Нет. Все как нужно.
ну тогда только первый вариант.

> Вот такой код должен быть?
ну чего паришься? с критической секцией только тормозов добавишь на ожидании, зачем для логирования? критично?

просто замени на PostMessage в исходном коде и проверь.


Юрий Зотов   (2015-12-16 13:17:00) [36]

> Alex4445   (16.12.15 13:02) [5]
> Разве сам SendMessage  - не гарантирует синхронизации ???

SendMessage гарантирует только то, что вызвавший ее поток сначала полностью выполнит обработку сообщения и лишь потом пойдет дальше.

Причем неважно, что код обработки сообщения написан в классе формы - выполнен этот код будет все равно в потоке, вызвавшем SendMessage.


Alex4445   (2015-12-16 13:16:00) [37]

По пред. примеру .. видимо тоже должно быть вот так ?


FCSGlobal  : TCriticalSection;

TMyThread = class (TThread)
FCS : TCriticalSection;
FA : Integer;
procedure Execute; override;
property  FAA : Integer read GetFA;
end;

procedure TMyThread.GetFA;
begin
 FCS.Enter;
 try
     Result := FA;
 finally  
  FCS.Leave;
end;
end;

procedure TMyThread.Execute;
begin
  while (not Terminated) do
       FCSGlobal.Enter;
        SendMessage (MyForm.Handle,WM_USER+1,FAA,0);  
       FCSGlobal.Leave;
      sleep (100);
  end;
end;


Alex4445   (2015-12-16 13:15:00) [38]

А кто ему мешает? Никто. Вот возможный сценарий:

1. Первый поток начал выполнять SendMessage, но еще не закончил (тем более, что в обработчике сообщения стоит Sleep(2000).

2. Второй поток начал выполнять SendMessage, потому что ему никто не мешает.

Нужна критическая секция (например).



A... видимо у меня путаница в выполнении ..

Я полагал, что основной поток в очереди сообщений берет SendMessage и "зависает" на нем пока не выполнит. И только после этого берет следующее сообщение SendMessage


Alex4445   (2015-12-16 13:12:00) [39]

На мой взгляд в этом случае получается синхронизация в синхронизации, если при этом через SendMessage еще какие-то данные передаются, то получаем потенциальный deadlock


TMyThread = class (TThread)
 FCS : TCriticalSection;
 FA : Integer;
 procedure Execute; override;
 property  FAA : Integer read GetFA;
end;

procedure TMyThread.GetFA;
begin
  FCS.Enter;
  try
      Result := FA;
  finally  
   FCS.Leave;
 end;
end;

procedure TMyThread.Execute;
begin
   while (not Terminated) do
        FCS.Enter;
         SendMessage (MyForm.Handle,WM_USER+1,FAA,0);   // -- DeadLock
        FCS.Leave;
       sleep (100);
   end;
end;


Юрий Зотов   (2015-12-16 13:11:00) [40]

> Как начал выполняться последующий SendMesssage
> пока не завершился предыдущий?


А кто ему мешает? Никто. Вот возможный сценарий:

1. Первый поток начал выполнять SendMessage, но еще не закончил (тем более, что в обработчике сообщения стоит Sleep(2000).

2. Второй поток начал выполнять SendMessage, потому что ему никто не мешает.

Нужна критическая секция (например).


Страницы:   1 2 3

Наверх

Титульная страница Поиск, карта сайта Написать письмо