我创建了一个日志记录应用程序,我有一个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.
答案 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 允许一个线程发出信号 到事件所具有的其他线程 发生。
答案 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;