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

发布于 2024-10-28 13:07:00 字数 2553 浏览 2 评论 0原文

我创建了一个日志记录应用程序,并且有一个 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.

I make a logging application and I have a LogEvent object with some string properties on it. I want to make this logging asynchronous and in another thread for not blocking the applications GUI thread.

Idea is that when I start application, some LogEventThread is running on the background all the time. If LogEvent property has changed then thread is executed, after execution thread suspends and waits another LogEvent object property change and run it again if new property change is captured.

Which are the best practises to design this?

EDIT:

I created an example. Please tell me if I'm on the correct path.

I have a 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 class:

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.

And thread class:

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

2024-11-04 13:07:00

您可以构建一个线程安全的 Queue 类,该类在 Producer-Consumer 模型中使用。您的 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 释放任何锁,因此如果 logger 线程正在等待队列,它将在调用 DoShutDown 后自动唤醒。然后我们通过调用WaitFor方法等待线程完成,最后我们销毁线程实例。

祝你好运

You can build a thread-safe Queue class which is used in a Producer-Consumer model. Your TThread descendant class should own an instance of this Queue class.

When you start your application, your queue is empty, and your logging thread is blocked waiting for queue. When you push a new string into the queue from the main thread, your queue pulses the logging thread, your logging thread wakes up and pops items from the queue until the queue is empty again.

To implement the queue in Delphi 2010, you can use TQueue generic class as the base type, and use System.TMonitor for synchronization. In Delphi XE, there is already a class which implements this for you, named TThreadedQueue. So If you are using Delphi XE, create an instance of TThreadedQueue, and in your logging thread try to call its PopItem() method.

EDIT:

Here is a sample logging thread which receives string logs:

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.

This TThread descendant uses a TThreadedQueue object as a buffer. When FLogQueue.PopItem is called, if the queue is empty, the thread goes to sleep, and waits until something is pushed into the queue. When an item is available in the queue, the thread pops it, and writes it to a file. This is a very simple code to just let you understand the basics of what you should do.

And here is a sample code for a form which is running in the context of main thread, and is logging a sample message:

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.

Here an instance of TLoggingThread is created when the form is initialized. When you press btnAddLog, a sample message is sent to the logger thread via its LogQueue property.
Take note of how the thread is terminated in FormDestroy method. First the thread is signaled that it is terminated, then we tell LogQueue to release any lock, so if the logger thread is waiting for the queue, it will wake up automatically after calling DoShutDown. Then we wait for the thread to finish up by calling WaitFor method, and eventually we destroy the thread instance.

Good Luck

你的心境我的脸 2024-11-04 13:07:00

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

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

In a multi-threaded application, use
TEvent to allow one thread to signal
to other threads that an event has
occurred.

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

苍景流年 2024-11-04 13:07:00

我将使用一个字符串队列,其中的关键部分位于 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;      

I would use a Queue of strings with a critical section inside push() and pop(). Inside the thread I would pop strings off, and log them. Inside the GUI thread I would push strings on the queue. I have done something similar before, and it is simple to implement.


Edit

Interface:

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

var
  CRITICAL_SECTION: TCriticalSection;

Implementation:

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;

Initialization

CRITICAL_SECTION := TCriticalSection.Create;

Finalization

FreeAndNil(CRITICAL_SECTION);

This code uses pointers to objects, but you can create storage for your strings inside the object, using a stringlist or array or whatever best fits your purpose, and change the pop and push methods to operate on your own storage.


Edit

Something like this:

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;      
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文