Как синхронизировать выполнение родительского / дочернего процесса? - PullRequest
7 голосов
/ 23 февраля 2012

Я хотел бы выполнить дочерний процесс и синхронизировать его (возможно, с Mutex), не дожидаясь завершения дочернего процесса:

Родитель:

program Project1;
{$APPTYPE CONSOLE}
uses
  Windows, ShellApi, SysUtils, Dialogs;

procedure ShellExecEx(Wnd: HWND; const AExeFilename, AParams: string);
const
  SEE_MASK_NOZONECHECKS = $00800000;
  SEE_MASK_WAITFORINPUTIDLE = $02000000;
  SEE_MASK_NOASYNC = $00000100;
var
  Info: TShellExecuteInfo;
begin
  FillChar(Info, SizeOf(Info), 0);
  Info.Wnd := Wnd;
  Info.cbSize := SizeOf(Info);
  Info.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_NOZONECHECKS or
    SEE_MASK_NOASYNC
    //or SEE_MASK_WAITFORINPUTIDLE (works only with UI app ???)
    //or SEE_MASK_NO_CONSOLE
    //or SEE_MASK_NOCLOSEPROCESS
    ;
  Info.lpVerb := '';
  Info.lpFile := PChar(AExeFilename);
  Info.lpParameters := PChar(AParams);
  Info.lpDirectory := PChar(ExtractFilePath(AExeFilename));
  Info.nShow := SW_SHOWNORMAL;
  if not ShellExecuteEx(@Info) then
    RaiseLastOSError;
  CloseHandle(Info.hProcess);
end;

var
  Mutex: THandle = 0;
  Error: DWORD;
begin
  OutputDebugString('Project1 : 1');

  ShellExecEx(0, 'Project2.exe', '');

  // synchronize
  repeat
    // attempt to create a named mutex
    Mutex := CreateMutex(nil, False, 'F141518A-E6E4-4BC0-86EB-828B1BC48DD1');
    Error := GetLastError;
    if Mutex = 0 then RaiseLastOSError;
    CloseHandle(Mutex);
  until Error = ERROR_ALREADY_EXISTS;

  OutputDebugString('Project1 : 3');
end.

Ребенок:

program Project2;
{$APPTYPE CONSOLE}
uses
  SysUtils, Windows, Dialogs;

var
  Mutex: THandle = 0;
begin
  OutputDebugString('Project2 : 2');
  // attempt to create a named mutex and acquire ownership
  Mutex := CreateMutex(nil, True, 'F141518A-E6E4-4BC0-86EB-828B1BC48DD1');
  if Mutex = 0 then RaiseLastOSError;

  // do something

  ReleaseMutex(Mutex);
  CloseHandle(Mutex); // <- at this point Program1.exe should exit the repeat loop

  ShowMessage('ok from Project2');
end.

Я ожидаю увидеть вывод:

Project1 : 1
Project2 : 2
Project1 : 3

Проблема в том, что иногда родительский элемент (Project1.exe) не выходит из цикла.
Что я делаю не так?

1 Ответ

11 голосов
/ 23 февраля 2012

У вас гонка на мьютексе.Вы надеетесь на следующую последовательность:

child:  create mutex
parent: open mutex
child:  destroy mutex

Но что может случиться так:

child:  create mutex
child:  destroy mutex
parent: open mutex (fails because mutex is destroyed)

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

В родительском:

  1. Создайте именованное событие.
  2. Установите событие как не сигнализированное.
  3. Создайте дочерний процесс.
  4. Подождите, пока событие не будет сообщено.

В дочернем процессе:

  1. Выполните некоторую обработку.
  2. Откройте именованное событие.
  3. Установите событие как сигнальное, освобождая родительский элемент от его ожидания.

На очень высоком уровне код, который вам нужен, будет выглядетькак это:

Родительский

Event = CreateEvent(nil, True, False, EventName);
//create it manual reset, set to non-signaled
ShellExecEx(....);
WaitForSingleObject(Event);

Ребенок

Event = CreateEvent(nil, True, False, EventName);
//do stuff
SetEvent(Event);

Я не включал проверку ошибок.Я уверен, что вы можете добавить немного.Вы также можете обнаружить, что класс-оболочка событий в SyncObjs более удобен.


Наконец, ваш код имеет занятый цикл.Это почти никогда не является решением любой проблемы.Если вы когда-нибудь пишете занятый цикл, вы должны воспринимать это как сигнал о том, что дизайн неправильный.Дело в том, что в вашем коде, если бы он мог работать, родительский процесс сожжет 100% загрузку ЦП, ожидая дочерний процесс.

...