пользовательское событие в lazarus

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

пользовательское событие в lazarus

Сообщение Widowmaker » 17.08.2011 14:16:18

Привет всем!

Сейчас пишу приложение, которое время от времени, ( достаточно редко, причём наиболее долго -- самый первый раз; все остальные разы существенно короче ), выполняет довольно длительные вычисления в безоконном классе, т.е. в чисто расчётном модуле. Начал под линуксом ( Ubuntu 10.04 + Lazarus 0.9.30 ), затем возникла надобность перенести сделанное под винду ( Win7 Starter + Lazarus 0.9.31 ). В процессе перенесения возникла трудность : если запускать не под отладкой, а в stand-alone режиме, то во время этих самых длительных вычислений под виндой тестовая форма теряет фокус ( например, если переключиться на другое окно ), то она перестаёт реагировать на внешние события вообще и "выцветает", пока длительные вычисления не завершатся. В диспетчере задач видно, что программа никуда не делась, не повисла, и работает. Под линуксом такой неприятности почему-то не возникает. :D

Попробовал бороться с напастью путём создания в расчётном модуле "пользовательского события", которое по идее должно периодически сообщать форме ( или другому безоконному расчётному модулю ) о завершении некоторого промежуточного этапа вычислений. Копал много и долго, однако всё время нарывался на рецепты и примеры именно для графики ( сообщения и пр. ) и многопоточной модели ( класс TThread ), пока не нашёл вот это : http://www.delphisources.ru/forum/showt ... 5%EA%F2%E0 , показавшееся мне близким к тому, что я хочу сделать. Пользовательское событие создать по этим рекомендациям создать вроде бы получилось, но теперь другая проблема -- как его правильно использовать. :roll: Вот что получилось в имитации длительных расчётов :

расчётный модуль-имитация :

Код: Выделить всё
unit FooClass;
//
{$mode objfpc}{$H+}
//
interface
//
uses
  Classes, SysUtils;
//
type
  TUserEvent = procedure ( Sender : TObject ) of object;
  //
  TFooClass = class ( TObject )
  //
  private
    { private declarations }
    NDim : integer;
    //
    fCalcDone : TUserEvent;
    //
    procedure FireUserEvent ( Sender : TObject );
  //
  protected
    { protected declarations }
  //
  public
    { public declarations }
    constructor Create ( ndim_ : integer );
    //
    destructor Destroy; override;
    //
    function FooMethod ( ) : double;
    //
    property CalcDone : TUserEvent read fCalcDone;
  end;
//
implementation
//
procedure  TFooClass.FireUserEvent ( Sender : TObject );
  begin
    if ( Assigned ( fCalcDone ) ) then fCalcDone ( Self );
  end;
//
constructor TFooClass.Create ( ndim_ : integer );
  begin
    inherited Create;
    //
    NDim := ndim_;
    if ( NDim < 1 ) then NDim := 1;
  end;
//
destructor TFooClass.Destroy;
  begin
    //
    inherited Destroy;
  end;
//
function TFooClass.FooMethod ( ) : double;
  var
    i, j, k, l : integer;
    summ, rnum : double;
  begin
    summ := 0;
    //
    for i := 0 to NDim - 1 do
      begin
        for j := 0 to NDim - 1 do
          begin
            for k := 0 to NDim - 1 do
              begin
                for l := 0 to NDim - 1 do
                  begin
                    rnum := 2.0 * Random - 1.0;
                    summ := summ + ln ( exp ( rnum ) );
                  end;
                // Fire User Event
                FireUserEvent ( Self );
              end;
          end;
      end;
    //
    summ := ln ( abs ( summ + 1.0 ) );
    //
    Result := summ;
  end;
end.


тестовая форма :

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

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
  ExtCtrls, FooClass;

type

  { TForm1 }

  TForm1 = class ( TForm )
    Button1: TButton;
    Button2: TButton;
    //
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    //
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormCreate(Sender: TObject);
  private
    { private declarations }
    procedure OnEvent ( fUserEvent : TUserEvent );
  public
    { public declarations }
  end;
//

var
  Form1: TForm1;
  //
  mFC : TFooClass;
  fCalcDone : TUserEvent;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  if ( Assigned ( mFC ) ) then FreeAndNil ( mFC );
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Form1.Close;
end;

procedure TForm1.Button1Click(Sender: TObject);
  var
    i, ndim, code : integer;
begin
  Val ( Edit1.Text, ndim, code );
  //
  if ( code <> 0 ) then
    begin
      ndim := 1;
      Edit1.Text := IntToStr ( ndim );
    end;
  //
  mFC := TFooClass.Create ( ndim );
  //
  Edit2.Text := 'n/a';
  Edit3.Text := 'n/a';
  //
  Edit1.Enabled := false;
  Edit2.Enabled := false;
  Edit3.Enabled := false;
  //
  Form1.Repaint;
  //
  for i := 1 to ndim do
    begin
      Edit3.Text := FloatToStr ( mFC.FooMethod ( ) );
      Edit2.Text := IntToStr ( i );
      //
      Form1.OnEvent ( fCalcDone );
      //
      Form1.Repaint;
    end;
  //
  FreeAndNil ( mFC );
  //
  Edit1.Enabled := true;
  Edit2.Enabled := true;
  Edit3.Enabled := true;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Button1.Caption := '&Start';
  Button2.Caption := '&Close';
  //
  Edit1.Text := '10';
  Edit2.Text := 'n/a';
  Edit3.Text := 'n/a';
end;

procedure TForm1.OnEvent ( fUserEvent : TUserEvent );
begin
  Self.Repaint; // при возникновении события форма должна перерисоваться
end;

end.


Компилируется и работает без ошибок, но ... ничего не происходит. :roll: Как правильно связать пользовательское событие в модуле FooClass с соответствующей процедурой в модуле формы ? Буду признателен за помощь. Ещё раз хотел бы подчеркнуть, что о многопоточности речь не идёт, по крайней мере, пока. Кстати, пока копал, нашёл интересный пример многопоточной программы без использования класса TThread : http://wiki.lazarus.freepascal.org/thre ... _project_1 .
У вас нет необходимых прав для просмотра вложений в этом сообщении.
Аватара пользователя
Widowmaker
новенький
 
Сообщения: 37
Зарегистрирован: 27.04.2011 18:32:04

Re: пользовательское событие в lazarus

Сообщение vada » 17.08.2011 14:49:34

У меня окнце с прогресс баром вот так работает. По смыслу та же фигня как и у Вас. Расчетный модуль генерит месаджи...

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

{$mode objfpc}{$H+}

Interface

Uses
  Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
  ComCtrls, LMessages, ExtCtrls, StdCtrls, KO206wTypes;

Type

  { TFormProgress }

  TFormProgress = Class(TForm)
    GroupBox1: TGroupBox;
    ProgressBar1: TProgressBar;
  Private
    { Private declarations }
    procedure MessageHandler(var Message: TLMessage); message LM_PROGRESS_MESSAGE;
    procedure SetProgressBarMax(Max: Integer);
    procedure SetProgressBarPos(Pos: Integer);
  Public
    { Public declarations }
  End;

Var
  FormProgress: TFormProgress;

Implementation

procedure TFormProgress.MessageHandler(var Message: TLMessage);
begin
  case Message.wParam of
    1: SetProgressBarMax(Message.lParam);
    2: SetProgressBarPos(Message.lParam);
  End;
  Repaint;
End;

procedure TFormProgress.SetProgressBarMax(Max: Integer);
begin
  ProgressBar1.Max := Max;
End;

procedure TFormProgress.SetProgressBarPos(Pos: Integer);
begin
  ProgressBar1.Position := ProgressBar1.Position + Pos;
  if (ProgressBar1.Position >= ProgressBar1.Max)
    then ProgressBar1.Position := ProgressBar1.Max;
End;

Initialization
  {$I progress.lrs}

End.


Самое главное тут MessageHandler в котором Repaint делается.
Аватара пользователя
vada
энтузиаст
 
Сообщения: 691
Зарегистрирован: 14.02.2006 13:43:17

Re: пользовательское событие в lazarus

Сообщение hinst » 17.08.2011 15:15:10

Widowmaker, если вам нужно, чтобы "морда" приложения не висла, а вычисления выполняются у вас в основном потоке, то просто создайте второй, дополнительный поток, и чтобы он с некоторым промежутком времени, например, sleep(100) вызывал Synchronize'ом метод вашего второго потока, а этот самый метод чтобы вызывал Application.ProcessMessages. Таким образом, дополнительный поток будет всё время (например, каждые 100 миллисекунд) прерывать основной поток, занятый вычислениями, и зависание пройдёт. (По крайней мере, мне так кажется)
Аватара пользователя
hinst
энтузиаст
 
Сообщения: 781
Зарегистрирован: 12.04.2008 18:32:38

Re: пользовательское событие в lazarus

Сообщение pda » 17.08.2011 16:01:22

hinst писал(а):например, sleep(100) вызывал Synchronize'ом метод вашего второго потока, а этот самый метод чтобы вызывал Application.ProcessMessages


Жесть какая! Просто внутрь тяжёлого циклов вставить вызов Application.ProcessMessages.
Аватара пользователя
pda
постоялец
 
Сообщения: 303
Зарегистрирован: 27.05.2005 19:59:53

Re: пользовательское событие в lazarus

Сообщение hinst » 17.08.2011 16:36:05

pda, в моём варианте можно быть уверенным в том, что метод будет вызываться ровно определённое количество раз в секунду, а если вставлять вызовы прямо в расчёт, то придётся ещё подумать, куда именно, и, в общем, может получиться неоптимально: скажем, он будет вызываться 100500 раз в секунду и отъедать значительную часть производительности.
Аватара пользователя
hinst
энтузиаст
 
Сообщения: 781
Зарегистрирован: 12.04.2008 18:32:38

Re: пользовательское событие в lazarus

Сообщение files32 » 17.08.2011 16:47:07

Код: Выделить всё
for i:=0 to 100000000 do
begin
делаем вычисления
if i mod 100 = 0 then //Через каждые сто итераций даем форме отрисоваться
Application.ProcessMessages;
end;
files32
новенький
 
Сообщения: 24
Зарегистрирован: 27.10.2007 13:42:41

Re: пользовательское событие в lazarus

Сообщение Widowmaker » 18.08.2011 02:59:39

К великому моему сожалению, Application.ProcessMessages, думаю, мне не подходит, т.к. он предназначен для обработки событий графического окна, а моя задача как раз в том и состоит, чтобы генерировать события в неграфических модулях и затем обрабатывать их где угодно -- хоть в окнах, хоть в безоконных объектах. Тогда уж больше подошёл бы экземпляр TCustomApplication, но у него своя особенность -- он придуман почти на все случаи жизни, и потому огромен и неуклюж. Применение его для этой довольно убогой задачи означало бы пальбу из пушек по воробьям. :D

Однако, кажется, выход всё же нашелся : пошерстив дельфи-ресурсы ещё немного, я разыскал некий шаблон для написания пользовательских событий : http://www.cyberforum.ru/delphi-beginne ... 74814.html . Навскидку вроде бы годится. Заодно стали понятны допущенные мною ошибки в коде в первом посте. :D

В прицепе -- перетолмаченный для Лазаруса дельфийский проект с оригинальным описанием, рабочий и хорошо комментированный; оригинальный проект доступен как rar-архив по ссылке выше. Важный момент : для его корректной работы дельфийский синтаксис, т.е. {$MODE Delphi}, должен быть включён по крайней мере в модуле формы. В ближайшее время попробую довести эту идею до ума. Всем откликнувшимся -- большое спасибо и удачи! :D
У вас нет необходимых прав для просмотра вложений в этом сообщении.
Аватара пользователя
Widowmaker
новенький
 
Сообщения: 37
Зарегистрирован: 27.04.2011 18:32:04

Re: пользовательское событие в lazarus

Сообщение shraibikus » 25.08.2011 21:49:06

Widowmaker писал(а):Application.ProcessMessages

Нужен в Windows для основного потока программы потому, что иначе сама Windows (в том числе и XP, просто не так наглядно, как Vista/7 - а лишь повесив надпись в титлбар програмного окна что-то вроде "программа не отвечает") начинает считать ее повисшей. Если-же будет вызов Application.ProcessMessages то windows успокоится.
Аватара пользователя
shraibikus
новенький
 
Сообщения: 36
Зарегистрирован: 22.09.2009 16:22:42
Откуда: Столица деревень

Re: пользовательское событие в lazarus

Сообщение Widowmaker » 26.08.2011 02:39:08

Да, всё правильно. Надо ещё учесть, что пользовательские события -- штука довольно медленная, поэтому в конце концов опытным путём был найден компромиссный вариант : в процессе длительных расчётов события возникают в расчётных модулях, стекаются в главную форму и там обрабатываются связкой Application.ProcessMessages; MainForm.Activate; MainForm.Refresh; . В основном цикле начинает работать таймер (он всё равно используется в программе), генерация событий в расчётных модулях и обработка их в главной форме прекращаются, и форма перерисовывается вышеупомянутой связкой по событиям таймера. Корректно работает и под линуксом, и под виндой. Однако, ИМХО, такое поведение маздая никак нельзя считать нормальным. :lol:

P.S. Кстати, по ходу дела возник вопрос : чем отличаются просто Timer и IdleTimer, и насколько принципиальна разница?
Аватара пользователя
Widowmaker
новенький
 
Сообщения: 37
Зарегистрирован: 27.04.2011 18:32:04

Re: пользовательское событие в lazarus

Сообщение pda » 06.09.2011 22:18:21

hinst писал(а):pda, в моём варианте можно быть уверенным в том, что метод будет вызываться ровно определённое количество раз в секунду, а если вставлять вызовы прямо в расчёт, то придётся ещё подумать, куда именно, и, в общем, может получиться неоптимально: скажем, он будет вызываться 100500 раз в секунду и отъедать значительную часть производительности.

У вас очень классный Synchronize, если через него всё так ровно работает. Я вот однажды делал неблокирующийся интерфейс в окне, где на события UI запускались тяжёлые расчёты. Где я переносил пользовательский ввод в поток, а обновления Ui обратно, так частые вызовы Synchronize мне изрядно тормозили программу. Оно не блокировалось, но программа изрядно тормозила.

Так что если ваш код недалеко, то я бы был признателен за небольшой бенчмарк. Сравнить скорость работы вашего варианта с отключенным потоком и вставленными ProcessMessages.
Аватара пользователя
pda
постоялец
 
Сообщения: 303
Зарегистрирован: 27.05.2005 19:59:53


Вернуться в Lazarus

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 236

Рейтинг@Mail.ru