Выполнить поток при изменении какого-либо свойства объекта - PullRequest
0 голосов
/ 31 марта 2011

Я создаю приложение для ведения журнала, и у меня есть объект LogEvent с некоторыми строковыми свойствами. Я хочу сделать эту запись асинхронной и в другом потоке, чтобы не блокировать поток приложений GUI.

Идея состоит в том, что когда я запускаю приложение, какой-то LogEventThread постоянно работает в фоновом режиме. Если свойство LogEvent изменилось, поток выполняется, после того как поток выполнения приостанавливает работу и ожидает другого изменения свойства объекта LogEvent и запускает его снова, если зафиксировано новое изменение свойства.

Каковы лучшие методы для разработки этого?

EDIT:

Я создал пример. Пожалуйста, скажите мне, если я на правильном пути.

У меня есть Форма1:

unit MainWindow;

interface

uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, TrackEventSenderThread, Generics.Collections, TrackEvent;

type
  TForm1 = class(TForm)
    btnTest: TButton;
    procedure FormCreate(Sender: TObject);
    procedure btnTestClick(Sender: TObject);

  private
    teqTrackEventSenderThread: TTrackEventSenderThread;
    trackEventQueue: TThreadedQueue<TTrackEvent>;

  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.btnTestClick(Sender: TObject);
var
  trackEvent: TTrackEvent;

begin
  trackEvent := TTrackEvent.Create;
  trackEvent.Category := 'test';
  trackEvent.Action := 'test';

  trackEventQueue.PushItem(trackEvent);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  trackEventQueue := TThreadedQueue<TTrackEvent>.Create;

  teqTrackEventSenderThread := TTrackEventSenderThread.Create(True);
  teqTrackEventSenderThread.TrackEventQueue := trackEventQueue;

  teqTrackEventSenderThread.Start;
end;

end.

Класс TrackEvent:

unit TrackEvent;

interface

type
  TTrackEvent = class(TObject)

  private
    sCategory: string;
    sAction: string;

  public
    property Category: string read sCategory write sCategory;
    property Action: string read sAction write sAction;

  end;

implementation

end.

И класс резьбы:

unit TrackEventSenderThread;

interface

uses Classes, Generics.Collections, TrackEvent;

type
  TTrackEventSenderThread = class(TThread)

  private
    trackEvent: TTrackEvent;
    teqTrackEventQueue: TThreadedQueue<TTrackEvent>;

  public
    constructor Create(CreateSuspended: Boolean);
    property TrackEventQueue: TThreadedQueue<TTrackEvent> read teqTrackEventQueue write teqTrackEventQueue;


  protected
    procedure Execute; override;

  end;

implementation

constructor TTrackEventSenderThread.Create(CreateSuspended: Boolean);
begin
  inherited;
end;

procedure TTrackEventSenderThread.Execute;
begin
  while not Terminated do
  begin
    if teqTrackEventQueue.QueueSize > 0 then
    begin
      trackEvent := teqTrackEventQueue.PopItem;

      //send data to server
    end;
  end;
end;

end.

Ответы [ 3 ]

2 голосов
/ 31 марта 2011

Вы можете создать потокобезопасный класс Queue, который используется в модели Producer-Consumer . Ваш класс-потомок TThread должен иметь экземпляр этого класса Queue.

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

Чтобы реализовать очередь в Delphi 2010 , вы можете использовать TQueue универсальный класс в качестве базового типа и использовать System.TMonitor для синхронизации. В Delphi XE уже существует класс, который реализует это для вас, с именем TThreadedQueue . Поэтому, если вы используете Delphi XE, создайте экземпляр TThreadedQueue и в своем потоке ведения журнала попытайтесь вызвать его метод PopItem ().

EDIT:

Вот пример потока логирования, который получает строковые журналы:

unit uLoggingThread;

interface

uses
  SysUtils, Classes, Generics.Collections, SyncObjs {$IFDEF MSWINDOWS} , Windows {$ENDIF};

type
  TLoggingThread = class(TThread)
  private
    FFileName : string;
    FLogQueue : TThreadedQueue<string>;
  protected
    procedure Execute; override;
  public
    constructor Create(const FileName: string);
    destructor Destroy; override;
    property LogQueue: TThreadedQueue<string> read FLogQueue;
  end;

implementation

{ TLoggingThread }

constructor TLoggingThread.Create(const FileName: string);
begin
  inherited Create(False);
  FFileName := FileName;
  FLogQueue := TThreadedQueue<string>.Create;
end;

destructor TLoggingThread.Destroy;
begin
  FLogQueue.Free;
  inherited;
end;

procedure TLoggingThread.Execute;
var
  LogFile : TFileStream;
  FileMode : Word;
  ALog : string;
begin
  NameThreadForDebugging('Logging Thread');
//  FreeOnTerminate := True;

  if FileExists(FFileName) then
    FileMode := fmOpenWrite or fmShareDenyWrite
  else
    FileMode := fmCreate or fmShareDenyWrite;
  LogFile := TFileStream.Create(FFileName,FileMode);
  try
    while not Terminated do
    begin
      ALog := FLogQueue.PopItem;
      if (ALog <> '')  then
        LogFile.Write(ALog[1],Length(ALog)*SizeOf(Char));
    end;
  finally
    LogFile.Free;
  end;
end;

end.

Этот потомок TThread использует объект TThreadedQueue в качестве буфера. Когда вызывается FLogQueue.PopItem, если очередь пуста, поток переходит в спящий режим и ожидает, пока что-то не будет помещено в очередь. Когда элемент доступен в очереди, поток извлекает его и записывает в файл. Это очень простой код, позволяющий вам понять основы того, что вы должны делать.

А вот пример кода для формы, которая выполняется в контексте основного потока и регистрирует пример сообщения:

unit fMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, uLogginThread;

type
  TfrmMain = class(TForm)
    btnAddLog: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnAddLogClick(Sender: TObject);
  private
    FLoggingThread : TLoggingThread;
  public
    { Public declarations }
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  FLoggingThread := TLoggingThread.Create(ExtractFilePath(Application.ExeName) + 'Logs.txt');
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  FLoggingThread.Terminate;
  FLoggingThread.LogQueue.DoShutDown;
  FLoggingThread.WaitFor;
  FreeAndNil(FLoggingThread);
end;

procedure TfrmMain.btnAddLogClick(Sender: TObject);
begin
  FLoggingThread.LogQueue.PushItem('This is a test log. ');
end;

end.

Здесь экземпляр TLoggingThread создается при инициализации формы. Когда вы нажимаете btnAddLog, образец сообщения отправляется в поток регистратора через его свойство LogQueue. Обратите внимание на завершение потока в методе FormDestroy. Сначала потоку сообщается о том, что он завершен, а затем мы сообщаем LogQueue об освобождении любой блокировки, поэтому, если поток средства ведения журнала ожидает очереди, он автоматически проснется после вызова DoShutDown. Затем мы ожидаем завершения потока, вызывая метод WaitFor, и в конечном итоге уничтожаем экземпляр потока.

Удачи

1 голос
/ 31 марта 2011

Я бы использовал Очередь строк с критическим разделом внутри push() и pop().Внутри нити я бы выскакивал строки и записывал их.Внутри потока GUI я бы вставил строки в очередь.Я делал нечто подобное раньше, и его легко реализовать.


Редактировать

Интерфейс:

TThreadSafeQueue = class(TQueue)
protected
  procedure PushItem(AItem: Pointer); override;
  function PopItem: Pointer; override;
  function PeekItem: Pointer; override;
end;

var
  CRITICAL_SECTION: TCriticalSection;

Реализация:

function TThreadSafeQueue.PeekItem: Pointer;
begin
  CRITICAL_SECTION.Enter;
  Result := inherited PeekItem;
  CRITICAL_SECTION.Leave;
end;

function TThreadSafeQueue.PopItem: Pointer;
begin
  CRITICAL_SECTION.Enter;
  Result := inherited PopItem;
  CRITICAL_SECTION.Leave;
end;

procedure TThreadSafeQueue.PushItem(AItem: Pointer);
begin
  CRITICAL_SECTION.Enter;
  inherited PushItem(AItem);
  CRITICAL_SECTION.Leave;
end;

Инициализация

CRITICAL_SECTION := TCriticalSection.Create;

Финализация

FreeAndNil(CRITICAL_SECTION);

В этом коде используются указатели на объекты, но вы можете создать хранилище для ваших строк внутри объекта, используя список строк или массив или любые другие подходящие варианты.своей цели и измените методы pop и push для работы с собственным хранилищем.


Edit

Примерно так:

procedure TMyThread.Execute;
var
  Msg: string;
begin
  while not Terminated do
  begin
    if FQueue.Count > 0 then
    begin
      Msg := FQueue.pop();
      PerformLog(Msg); {Whatever your logging method is}
    end;
    Sleep(0); 
  end;
end;      
1 голос
/ 31 марта 2011

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

http://docwiki.embarcadero.com/VCL/en/SyncObjs.TEvent

...