Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > Помогите "временно" заблокировать процедуру


Автор: oleg153 4.5.2009, 19:50
Есть 3 приложения :
- Клиент (1)
- Сервер-Клиент (2)
- Сервер (3)

Основное - (2) . Описаны две процедуры которые работают с MSSQL через ADO. Процедура-2 вызывается Клиентом(1) ,  Процедура-1 вызывается  Сервер-Клиентом (2).
Обе процедуры исполняются на Сервер-Клиенте.
В процесе исполнения Процедуры-1 , Сервер (2) - обращается к внешнему Серверу(3) .

Проблемма в том что НИКАК ни через Критич. Секции, Ни через Мьютексы не могу заставить  процедуру-2, ждать пока не отработается процедура-1. 
Как только я вызываю метод реализованый на Сервере (3) вся защита слетает и начинает отрабатываться процедура-1 запущеная Клиентом.

Код

  private
    { Private declarations }
    CritSec:TRTLCriticalSection;
    DServ:Variant;
    hMutex   : THandle;
  public
    { Public declarations }
    procedure LockForm;
    procedure UnlockForm;
    procedure SetData1;
    procedure SetData2;
    procedure SetPause(MS:integer);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}
procedure TForm1.SetPause(MS:integer);
begin
 Sleep(MS)
end;

procedure TForm1.SetData1;
begin
 if WaitForSingleObject(hMutex,INFINITE) = WAIT_OBJECT_0  then
 try
  with ADOQuery1 do
   begin
    Close;
    Open;
    if RecordCount >0 then
     begin
      First;
      while not Eof do
        begin
         Edit;
         FieldByName('lockState').AsString:= 'lock';
         Post;
          // для тестирования.  Внешняя задержка
         if VarType(DServ) = varDispatch then
          begin
           DServ.Test1(7000);
          end
          else
          begin
           ShowMessage('Нет подключенного сервера');
          end;
         Next;
        end;
     end;
   end; //with
 finally
  ReleaseMutex(hMutex);
 end;
end;

procedure TForm1.SetData2;
begin
 if WaitForSingleObject(hMutex,INFINITE) = WAIT_OBJECT_0  then
 try
  with ADOQuery1 do
   begin
    Close;
    Open;
    if RecordCount >0 then
     begin
      First;
      Edit;
      FieldByName('lockState').AsString:= 'unlock';
      Post;
     end;
   end; //with
 finally
  ReleaseMutex(hMutex);
 end;
end;




procedure TForm1.Button1Click(Sender: TObject);
begin
// try
//  LockForm;
  SetData1;
// finally
//  UnlockForm;
// end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
 //Подключение к серверу.
  DServ := CreateOLEObject('DataServer.OfflineServer1');
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
 //Отключение от сервера.
 if VarType(DServ) = varDispatch then
  begin
   DServ := Unassigned;
  end
  else
  begin
   ShowMessage('Нет подключенного сервера');
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
 try
  {Открываем сеанс связи с б.д.}
  ADOConnection1.Open;
 except
  ShowMessage('Не удалось установить связь с б.д., сервер не доступен.');
 end;
 {инициализируем Критическую Секцию}
 InitializeCriticalSection(CritSec);

 hMutex   := CreateMutex(nil,false,'TerminalMutex');
 if hMutex = 0 then  RaiseLastWin32Error;

 memo1.Clear;

 end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
 {Закрываем сеанс связи с базой данных}
  ADOConnection1.Close;
 {Закрываем Критическую секцию}
  DeleteCriticalSection(CritSec);

 CloseHandle(hMutex);
end;

{Захват потоком критической секции}
procedure TForm1.LockForm;
 begin
  EnterCriticalSection(CritSec);
 end;

 {Освобождение потоком критической секции}
procedure TForm1.UnlockForm;
 begin
  LeaveCriticalSection(CritSec);
 end;
 

В общем как доходит Процедура-1 до  DServ.Test1(7000);

Вся защита срывается и начинает отрабатываться  procedure TForm1.SetData2 которая в этот момент ждет исполнения вызваная Клиентом (1).

Код

procedure Ttest15.KlientSetData(const ScriptCode: WideString);
begin
// try
//  Form1.LockForm;
  Form1.SetData2;
// finally
//  Form1.UnlockForm;
// end;
end;





Мне НУЖНО чтоб Процедура-1 отработалась до конца и только потом запустилась ждущая Процедура-2

Подскажите КАК это сделать. Бьюсь уже вторые сутки....

Автор: MetalFan 5.5.2009, 08:34
что-то прям чудеса какие-то. на первый взгляд работа с мютексами вроде бы корректная.

Автор: CodeMonkey 5.5.2009, 09:06
Может и глупость скажу, но...  у вас процедура-1 и процедура-2 выполняются в разных потоках? Добавьте логгинг GetCurrentThreadId.
Если в одинаковых, то, скорее всего, ситуация выглядит как-то так: запускается процедура-1, берёт блокировку, где-то внутри себя вызывает Application.ProcessMessages (или аналог) - в этот момент запускается сидящая в очереди процедура-2, она работает, поскольку текущий поток уже владеет блокиовкой, процедура-2 завершается, затем заканчивается процедура-1 и отпускает блок.

Автор: oleg153 5.5.2009, 13:15
Спасибо. Кое что понял

Я проверил - по        Memo1.Lines.Add(inttostr(GetCurrentThreadId()); 
получается что поток один. 

Я из того что читал понял что VCL (основной поток) и поток COM - разные, даже если сервер создан как STA. Но похоже я ошибся.

Значит все варианты блокировки с использованием секций и мьютексов не пройдут. Или нужно создавать отдельные потоки

Но почему запускается Процедура-2 ? Ведь вроде смысл одного потока - ждать окончания 1-ой процедуры ?

И что делать если НЕ разделять процедуры по разным потокам ? Как не позволить запускаться  

Автор: CodeMonkey 5.5.2009, 13:46
Цитата
Но почему запускается Процедура-2 ?

Если вы привели полный код, то вот это у вас кто: DServ.Test1?
Ещё ShowMessage содержит цикл Application.ProcessMessages (если это действительно причина).

Автор: oleg153 5.5.2009, 14:01
Код

 //Подключение к серверу.
  DServ := CreateOLEObject('DataServer.OfflineServer1');


DServ - соответственно Сервер-3. Еще один сом-сервер также реализованый как STA (однопотоковый) , который производит обработку переданых ему в процедуре SetData1 - данных.

В Данном примере я на сервере-3 просто организовал задержку и передаю время задержки. Это для тестирования. В реали я передаю данные которые обрабатываются 0.5-5 сек и возвращают результат обработки. Возврат не стал делать так как его наличие не влияет на результат, все равно в потоке после вызова DServ.Test1 начинает работать Процедура-2.
Т.е. в основном потоке очередь от Процедуры-1 передается на П-2 хотя П-1 не отработалась до конца.

К сожалению [B][Ещё ShowMessage содержит цикл Application.ProcessMessages (если это действительно причина)./B] - это мне не понятно. Если можно поясните.

Автор: CodeMonkey 5.5.2009, 15:33
Я, признаться, в COM полный 0, поэтому при ваших объяснениях сразу забуксовал. Давайте вам кто-нибудь другой что-нить скажет smile 

Автор: Palladin 5.5.2009, 20:55
Мдя, ну на крайняк:
Создаешь переменную, обнуляешь её, когда заканчивается первая процедура присваеваешь переменной, ну к примеру значение '1', а весь код второй процедуры заключаешь в 
Код

if(a=1)then
 begin
  весь код второй процедуры
 end;


 smile

Добавлено через 3 минуты и 41 секунду
Не прочитал все, поэтому сверху чушь.
Однако, вот ты передаешь значения в первой процедуре, во второй попробуй проверять, ну мол передалось ли последнее значение первой процедуры, если да то выполняем вторую процедуру, если нет...
Получается тоже if(a=1) только вместо а проверяй мол ввелось ли или передалось ли чтонить...

Автор: MetalFan 6.5.2009, 07:50
Цитата(oleg153 @  5.5.2009,  14:01 Найти цитируемый пост)
Еще один сом-сервер также реализованый как STA (однопотоковый)


Цитата(oleg153 @  5.5.2009,  14:01 Найти цитируемый пост)
К сожалению [B][Ещё ShowMessage содержит цикл Application.ProcessMessages (если это действительно причина)./B] - это мне не понятно.

тогда примерно понятно. afaik STA реализуется с пом. очереди сообщений того потока, в котором работает ком-сервер.
соотв. как только в одной процедуре начинается проверка очереди сообщений, а кто-то обращается к другой процедуре, то это другая процедура и начнет выполняться. да и мютексы и крит.секции в этом случае безполезны - это средства межпроцессовой и межпотоковой синхронизации. тогда действительно проще завести переменную-флаг, а еще лучше не пользовать ShowMessage, а пользовать какой-нибудь MessageBox или вообще писать в лог.

Автор: oleg153 6.5.2009, 11:29
В общем вроде понял.

У меня был Сервер-2 однопотоковый. Соответственно ни секции ни мьютексы не работали. Сом-методы вызывались в основном, и захват Крит.секции этим потоком позволял выполнять как процедуры вызваные Сервером, так и через СОМ.

Сделал его в модели Free - т.е. многопотоковым. Тогда Клинт и Сервер могут запускать одну процедуру в разных потоках и можно использовать крит. секцию. 
Но если в сом-потоке будет присутствовать обращение к VCL элементам Сервера-2 , тогда все просто встанет, так как для этого нужно передать управление основному потоку.
Соответственно сом-поток ( в апартаменте) не может завершить действия.

В общем , чтоб не городить флаги, придется делать многопотоковый сервер и просто внимательно следить за обращением к VCL-компонентам.  Обращаться к ним только из основного потока,
когда он владеет Крит.секцией.

Пока на первый взгляд так должно работать. В вышеприведенном коде вроде работает.


Автор: CodeMonkey 6.5.2009, 13:20
Ну, для обращения к главному потоку из любого вторичного есть Synchronize (также и в статическом варианте), либо удобная оболочка для него EnterMainThread/LeaveMainThread из AsyncCalls.

Автор: oleg153 6.5.2009, 15:07
Если можно, проясните, сам пока не понял. 

Есть Основной поток Сервера  и COM-поток созданный Сервером

Для исключения доступа к процедуре описаной в главной форме использую Крит. секцию.

СОМ-поток захватывает секцию и работает с Таблицами (MSSQL) через ADO.

Если в момент работы когда Крит.сек. захвачена Основной поток пытается запустить процедуру, он натыкается на блокировку через Крит.секс. и становится в ожидание.

И вот если в этот момент активный СОМ-поток пытается что либо сделать с экранными компонентами (Memo, DBGrid ) - тогда все умирает. Совсем. Тупик.

Понимаю что это из-за блокировки основного потока Критической секцией.

Но поможет ли здесь Synchronize ? Ведь основной поток заблокирован на попытке EnterCriticalSection(CS); 

Подскажите, так как с потоками только разбираюсь и боюсь напортачить.

Автор: CodeMonkey 6.5.2009, 16:01
Цитата
Если в момент работы когда Крит.сек. захвачена Основной поток пытается запустить процедуру, он натыкается на блокировку через Крит.секс. и становится в ожидание.

Мне просто показалось, что обе процедуры запускаются из вторичных потоков. А так, вы, конечно, правы - у вас будет dead-lock - два потока будут ждать друг-друга.

Если из главного потока нужно запустить что-то, что требует блокировки, которую может держать другой поток, который может запросить главный поток, который живёт в доме, который построил джек, то лучше бы это "что-то" запустить в ещё одном отдельном потоке, дабы не блокировать главный. Блокировать главный поток в VCL-приложении - это не очень хорошо.

Если пойдёте таким путём, то к вашим услугам Synchronize.
Если же нет - то тут только отправка сообщений. Да и то, сообщение не будет обработано, пока главный поток блокирован. Так что, ситуация с dead-lock-ом может даже и повториться, если вы используете SendMessage.

Автор: oleg153 6.5.2009, 16:18
Да. Спасибо.

Я Тоже прихожу к выводу что придется запускать в отдельном, не COM потоке.  Теперь осталось внимательно разобратся с потоками.

Автор: oleg153 6.5.2009, 17:32
Пока еще не понял как совместить Synchronize и ADO

Допустим в потоке нужно выполнить код :

Код

  ADOQuery1.Close;
  ADOQuery1.Open;
  if ADOQuery1.RecordCount >0 then
   begin
    ADOQuery1.First;
    while not ADOQuery1.Eof do
      begin
       ADOQuery1.Edit;
       ADOQuery1.FieldByName('lockState').AsString:= 'lock';
       ADOQuery1.Post;
        // для тестирования.  Внешняя задержка через Сервер
       if VarType(DServ) = varDispatch then
        begin
         DServ.Test1(6000);
        end
        else
        begin
         ShowMessage('Нет подключенного сервера');
        end;
       ADOQuery1.Next;
      end;
   end;

Здесь ADOQuery через TDataSource отображает в таблице DBGrid результаты. Причем динамически. 
Это можно увязать с Synchronize ?

Автор: MetalFan 7.5.2009, 11:46
Цитата(oleg153 @  6.5.2009,  17:32 Найти цитируемый пост)
Здесь ADOQuery через TDataSource отображает в таблице DBGrid результаты. Причем динамически. 
Это можно увязать с Synchronize ? 

ого, какая жесть...
из доп. потока идет работа с компонентами (ADO), что лежат на форме, созданной и показываемой в осн.потоке?!
тут вижу два варианта: 
1. создавать ADO компоненты (для получения данных) в потоке, а потом "отдавать" их осн.потоку, чтобы он "подкючил" их для отображения через DataSource.
2. Отключать, на время работы с базой в доп.потоке, "визуализаторы" данных...

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)