Долгие отчеты, форма «ожидания» в отдельном потоке.

IBX, FIBPlus, UIB, ADO, .Net и прочее-прочее-прочее, в общем все, что относится к созданию приложений, работающих с InterBase, Firebird и Yaffil - клиент-серверных, трехзвенных, консольных и т.п.

Модератор: kdv

Ответить
aes
Сообщения: 43
Зарегистрирован: 05 фев 2007, 07:29

Долгие отчеты, форма «ожидания» в отдельном потоке.

Сообщение aes » 14 окт 2009, 16:34

Добрый день.
В проекте требуется выполнить формирование отчетов (запросы к БД длятся от 1 до 60 сек) с отображением формы ожидания и возможностью отмены в любой момент. При отмене долгого запроса (поскольку это не происходит мгновенно) форма ожидания должна отображаться с сообщением «отмена операции». SQL-сервер Firebird2.5Beta2, среда разработки Delphi 7.
Изучив статью http://mbo88.narod.ru/ToC.html плюс ветки, посвященные потокам, здесь на форуме, пошел таким путем:
1.Объявляю два класса

Код: Выделить всё

TSplashThread = class(TThread) //поток для отображения формы ожидания
private
  tstbCancel: boolean; //признак «отмены»  
  procedure tstCheckStatusTerminate; //проверка статуса «отмена»
protected
  procedure Execute; override;
end;

TExecThread = class(TThread) //поток для расчета и обновления формы ожидания
private
  tetiProgress: integer; // прогресс бар     
  procedure tetSetProgress; // установка прогресс бара на форме ожидания
  procedure tetSetSplashTerminate; //завершение расчета
protected
  procedure Execute; override;
end;
2.При вызове отчета создаю форму ожидания (в главном потоке), отображаю ее и стартую поток для отображения формы ожидания (назову его для краткости потоком ожидания)

Код: Выделить всё

if not assigned(formSplash) then
  formSplash := TformSplash.Create(Application);
formSplash.Show; 
SplashThread:=TSplashThread.Create(true);
SplashThread.FreeOnTerminate:=true;
SplashThread.tstbCancel := False;
SplashThread.Priority:=tpLower;
SplashThread.Resume;
3.Поток ожидания запускает поток расчетов. Здесь же, при отмене расчетов для снятия длительного запроса создается датамодуль, устанавливающий собственное соединение с базой, с query:

Код: Выделить всё

delete from MON$ATTACHMENTS where MON$ATTACHMENT_ID=:I_CUR_CONNECT
I_CUR_CONNECT получаю в Execute блоке потока расчета, который «передаю» в главный поток по Synchronize. Вообще все взаимодействие дочерних и основного потока (с VCL) делаю через Synchronize.
Код метода Execute потока ожидания:

Код: Выделить всё

procedure TSplashThread.Execute;
var ExecThread: TExecThread;
begin
  ExecThread:=TExecThread.Create(True);
  ExecThread.FreeOnTerminate:=True;
  ExecThread.Priority:=tpLower;
  ExecThread.tetiProgress := 0;
  ExecThread.Resume;
  while (not tstbCancel) and (not Terminated) do //бесконечный цикл
  begin
    Synchronize(tstCheckStatusTerminate);
    Application.ProcessMessages;
    sleep(1);
  end;
  //отмена долгого запроса
  dmDataDelConnects := TdmDataDelConnects.Create(Application);
  dmDataDelConnects.fibDB.Connected := True;
  dmDataDelConnects.fibqDelCC.Transaction.StartTransaction;
  dmDataDelConnects.fibqDelCC.ParamByName('I_CUR_CONNECT').AsInteger := tstiCurConnect;
  dmDataDelConnects.fibqDelCC.ExecQuery;
  dmDataDelConnects.fibDB.Connected := False;
  FreeAndNil(dmDataDelConnects);
  ExecThread.Terminate;  //останов потока расчета
end;
4.В потоке расчета устанавливаю собственное соединение с БД, открываю датасет с запросом для формирования отчета. При фетче записей проверяю Terminated потока. При любом условии завершения потока расчета выполняется процедура tetSetSplashTerminate:

Код: Выделить всё

procedure TExecThread.tetSetSplashTerminate;
begin
  if assigned(dmDataReps) then
  begin
    dmDataReps.fibDB.Connected := False;
    FreeAndNil(dmDataReps);
  end;
  if Assigned(formSplash) then FreeAndNil(formSplash); //уничтожаю форму ожидания
end;
Все вышесказанное вроде бы логично, но не работает так, как нужно. Плюс часто выпадают access violation в самых неожиданных местах. Поскольку с потоками работаю впервые (так уж сложилось), прошу помощи.
1.В связи с тем, что часто и где попало выпадают access violation, явно нарушен сам принцип работы с потоками. Ткните пожалуйста в самые проблемные места вышеописанного алгоритма.
2.При выполнении кода, отменяющего долгий запрос, сразу после ExecQuery (поток ожидания, операция отмены), почему-то с экрана пропадает форма ожидания. Закоментарил данный кусок кода, отменяющего запрос, запустил проект, отменил формирование расчета, после чего сразу же в Ибэксперте удалил строку в MON$ATTACHMENTS – эффект тот же, пропала форма ожидания. Чем вызвано такое поведение? Соответственно, если не удаляю, то форма отображается до завершения долгого запроса.
3.Проект запущен в Delphi. Выполняю вновь операцию отмены отчета, сразу после ExecQuery вылетает ошибка EOSError ‘System error. Code: 5. Отказано в доступе’. Это нормально? Проект после этого работает.
4.Наверняка кто-то уже решал подобную задачу. По возможности, поделитесь пожалуйста своими идеями, хотя бы в общих чертах описав подход и принцип реализации такого функционала.
5.Есть еще одна проблема – обновление по таймеру текущих данных, в данном конкретном проекте – значений электропотребления для выбранной расчетной группы. Проблема в том, что при выборе детализации «год по месяцам» запрос на обновление выполняется по времени до минуты. Возможно ли вынести это обновление в отдельный поток, предусмотрев досрочную отмену операции, и каким образом (принцип в общих чертах)?

aes
Сообщения: 43
Зарегистрирован: 05 фев 2007, 07:29

Re: Долгие отчеты, форма «ожидания» в отдельном потоке.

Сообщение aes » 16 окт 2009, 15:59

Взяв пару дней на размышления и эксперименты я таки поборол проблему, может быть кому пригодится. Ни в коем случае не претендую на истину в последней инстанции, даже допускаю, что все ранее и далее сказанное является особо редким и сложным методом в проктологии, но тем не менее оно работает.
1. Выпадение access violation я поборол, моя ошибка была не в организации работы с потоками, а в коде расчета, который "лез" куда не надо.
2. Правильный код для отмены долгого запроса выглядит так:

Код: Выделить всё

delete from MON$STATEMENTS
where MON$ATTACHMENT_ID=:I_CUR_CONNECT and
      MON$TRANSACTION_ID=:I_CUR_TRANS and
      MON$STATE=1
Срабатывает мгновенно.
3. В потоке расчета обрыв коннекта корректно обрабатывается. Соответственно, ошибка конечно вылетает, но на работе проекта не сказывается.
4. Тут собссно нечего добавить.
5. Данную проблему попробую решить с помощью временных таблиц. В потоке расчетов все результаты будут сохранены во временной таблице, выборка из которой и будет отображена пользователю.

Кузнецов Евгений
Сообщения: 144
Зарегистрирован: 16 фев 2006, 22:36

Re: Долгие отчеты, форма «ожидания» в отдельном потоке.

Сообщение Кузнецов Евгений » 16 окт 2009, 22:41

Доброго времени суток!

To aes
А зачем форму ожидания помещать в отдельный поток?
Все равно из-за этого участка

Код: Выделить всё

  while (not tstbCancel) and (not Terminated) do //бесконечный цикл
  begin
    Synchronize(tstCheckStatusTerminate);
    Application.ProcessMessages;
    sleep(1);
  end;
Вы практически все время будете сидеть в главном. Почитайте эту статью еще раз, кроме Synchronize, есть и более эффективные методы.
На мой взгляд, лучше работу с формой ожидания поместить в главный поток, ожидать в нем завершения потока расчетов через MsgWaitForSingleObject, а вот отмену операции делать как раз в фоновом потоке (и также ожидать его завершения через MsgWaitForSingleObject).

Далее, если Вы уж создаете DataModule, лучше реализовать всю логику отмены в нем, а не работать с его "внутренностями" напрямую:

Код: Выделить всё

  dmDataDelConnects := TdmDataDelConnects.Create(Application);
  try
    dmDataDelConnects.CancelStatement(tstiCurConnect);
  finally
    FreeAndNil(dmDataDelConnects);
  end;

aes
Сообщения: 43
Зарегистрирован: 05 фев 2007, 07:29

Re: Долгие отчеты, форма «ожидания» в отдельном потоке.

Сообщение aes » 22 окт 2009, 07:35

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

Ответить