在某些对象属性更改上执行线程
我创建了一个日志记录应用程序,并且有一个 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您可以构建一个线程安全的 Queue 类,该类在 Producer-Consumer 模型中使用。您的 TThread 后代类应该拥有此 Queue 类的一个实例。
当您启动应用程序时,您的队列是空的,并且您的日志记录线程被阻塞等待队列。当您从主线程将新字符串推入队列时,您的队列会向日志记录线程发出脉冲,您的日志记录线程将被唤醒并从队列中弹出项目,直到队列再次为空。
要在Delphi 2010中实现队列,可以使用TQueue 泛型类作为基类型,并使用System.TMonitor同步。在 Delphi XE 中,已经有一个类可以为您实现此功能,名为 TThreadedQueue。因此,如果您使用 Delphi XE,请创建 TThreadedQueue 的实例,并在日志记录线程中尝试调用其 PopItem() 方法。
编辑:
这是一个接收字符串日志的示例日志记录线程:
此 TThread 后代使用 TThreadedQueue 对象作为缓冲区。当调用 FLogQueue.PopItem 时,如果队列为空,线程将进入睡眠状态,并等待,直到有东西被推入队列。当队列中有可用项目时,线程将其弹出,并将其写入文件。这是一个非常简单的代码,只是让您了解应该做什么的基础知识。
下面是在主线程上下文中运行并记录示例消息的表单的示例代码:
这里在初始化表单时创建 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:
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:
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
http://docwiki.embarcadero.com/VCL/en/SyncObjs.TEvent
http://docwiki.embarcadero.com/VCL/en/SyncObjs.TEvent
我将使用一个字符串队列,其中的关键部分位于
push()
和pop()
内。在线程内部,我会弹出字符串并记录它们。在 GUI 线程内,我会将字符串推送到队列中。我之前也做过类似的事情,实现起来也很简单。编辑
接口:
实现:
初始化
完成
此代码使用指向对象的指针,但您可以使用字符串列表或数组或最适合您目的的任何内容为对象内的字符串创建存储,并更改 pop 和 push 方法以自行操作贮存。
编辑
这样的东西:
I would use a Queue of strings with a critical section inside
push()
andpop()
. 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:
Implementation:
Initialization
Finalization
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: