在某些对象属性更改上执行线程

时间:2011-03-31 06:32:32

标签: multithreading delphi delphi-xe

我创建了一个日志记录应用程序,我有一个LogEvent对象,上面有一些字符串属性。我想使这个日志记录异步,并在另一个线程中不阻塞应用程序GUI线程。

想法是,当我启动应用程序时,一些LogEventThread一直在后台运行。如果LogEvent属性已更改,则在执行线程挂起并等待另一个LogEvent对象属性更改后执行线程,并在捕获新属性更改时再次运行它。

设计此方法的最佳做法是什么?

编辑:

我创建了一个例子。如果我走的正确,请告诉我。

我有一个Form1:

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 个答案:

答案 0 :(得分:2)

您可以构建一个在Producer-Consumer模型中使用的线程安全的Queue类。您的TThread后代类应该拥有此Queue类的实例。

启动应用程序时,队列为空,并且阻止日志记录线程等待队列。当您从主线程将新字符串推入队列时,您的队列将激活日志记录线程,您的日志记录线程将被唤醒并从队列中弹出项目,直到队列再次为空。

要在 Delphi 2010 中实现队列,您可以使用TQueue泛型类作为基本类型,并使用System.TMonitor进行同步。在 Delphi XE 中,已经有一个为您实现此功能的类,名为TThreadedQueue。因此,如果您正在使用Delphi XE,请创建一个TThreadedQueue实例,并在您的日志记录线程中尝试调用其PopItem()方法。

修改

以下是一个接收字符串日志的示例日志记录线程:

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 :(得分:1)

  

在多线程应用程序中,使用    TEvent 允许一个线程发出信号   到事件所具有的其他线程   发生。

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

答案 2 :(得分:1)

我会在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方法以在您自己的存储上运行。


修改

这样的事情:

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;