【问题标题】:How to handle Log in a Threaded manner in Delphi如何在 Delphi 中以线程方式处理日志
【发布时间】:2013-07-04 14:29:04
【问题描述】:

德尔福 XE2

我有一个带有 TMemo 的表单,我想显示应用程序启动的多个服务中发生的情况。

我正在运行的内容:

  • idHTTPServer 与 idContext 一起运行以响应请求
  • 从 Dropbox 下载更新的线程
  • idUDPServer 响应 UDP 请求
  • 另一个线程负责处理一些数据库内容。
  • 主应用线程也需要添加日志

基本上,我需要知道如何创建一个标准的、统一的、线程安全的方式来将日志消息传送到我的 TMemo 并让用户了解正在发生的事情。

【问题讨论】:

    标签: delphi thread-safety


    【解决方案1】:

    基本上,您可以构建一个接收所有消息的线程(这里,它是一个函数 AddEvent)。消息被排队(和时间戳)并尽可能写到备忘录中(如果您处于繁重的负载下......)。

    如果超过行数别忘了清理备忘录,添加异常处理等...

    我使用这样的东西:

        TThreadedMsgEvent = class( TThread )
        private
              FLock : TCriticalSection;
              FStr : TQueue<String>;
              FMemo : TMemo;
              function GetEvent : String;
        protected
              procedure Execute; override;
        public
              procedure AddEvent( aMsg : String );
    
              constructor Create( AMemo: TMemo );
              destructor Destroy; override;
        end;
    implementation
    
    { TThreadedMsgEvent }
    
    procedure TThreadedMsgEvent.AddEvent(aMsg: String);
    begin
         FLock.Acquire;
         FStr.Enqueue( FormatDateTime('DD/MM/YY HH:NN:SS.ZZZ',Now)+ ' : '+ aMsg );
         FLock.Release;
    end;
    
    constructor TThreadedMsgEvent.Create(aMemo: TMemo);
    begin
      inherited Create(True);
    
      FreeOnTerminate := False;
      FOnMessage := ACallBack;
      FStr := TQueue<String>.Create();
      FLock      := TCriticalSection.Create;
      FMemo := aMemo;
      Resume;
    end;
    
    destructor  TThreadedMsgEvent.Destroy; override;
    begin
          FreeAndNil( FStr );
          FreeAndNil( FLock );
    end;
    
    procedure TThreadedMsgEvent.Execute;
    begin
      while not Terminated do
      begin
    
          try
             if (FStr.Count > 0) then
             begin
                  if Assigned( aMemo ) then
                  begin
                        TThread.synchronize( procedure
                                             begin
                                                FMemo.Lines.Add( GetEvent );    
                                             end; );
                  end;
    
             end;
          except
          end;
          TThread.Sleep(1);
      end;
    
    end;
    
    function TThreadedMsgEvent.GetEvent: String;
    begin
         FLock.Acquire;
         result := FStr.Dequeue;
         FLock.Release;
    end;
    

    您还可以使用 Windows 消息通知此线程。这可能会更容易,因为您不需要在您的课程中引用此线程。

    【讨论】:

    • 您应该使用阻塞队列。这需要一个事件来表明项目可用。周围有很多很好的例子和课程。那个伪忙循环不好。
    • @DavidHeffernan 你能指出这个阻塞队列的一个很好的例子吗?
    • TThreadedQueue 是否能够做同样的事情?如果所有线程都引用相同的 TThreadedQueue 对象来添加字符串并在主线程中使用字符串。安全吗?它没有错误吗?
    • @eelias, TThreadedQueue 如果您有 Delphi-XE2 更新 4 或更高版本,则可以使用。请参阅TThreadedQueue not capable of multiple consumers? 了解更多信息。
    • 为此目的使用线程不仅是多余的,而且在这种情况下会不必要地降低性能。首先是使用 David 提到的繁忙循环,其次是导致大量额外的和不必要的线程上下文切换。摆脱线程,创建一个简单的线程安全日志队列,并在第一项添加到队列时让它向 GUI 线程发布一条消息。在处理该消息时,一次消耗来自 GUI 线程的所有排队项。
    【解决方案2】:

    既然您已经在使用 Indy,您可以使用 Indy 的 TIdSync(同步)或 TIdNotify(异步)类安全地访问 TMemo。出于简单的日志记录目的,我会使用TIdNotify,例如:

    type
      TLog = class(TIdNotify)
      protected
        FMsg: string;
        procedure DoNotify; override;
      public
        class procedure LogMsg(const AMsg; string);
      end;
    
    procedure TLog.DoNotify;
    begin
      Form1.Memo1.Lines.Add(FMsg);
    end;
    
    class procedure TLog.LogMsg(const AMsg: string);
    begin
      with TLog.Create do
      try
        FMsg := AMsg;
        Notify;
      except
        Free;
        raise;
      end;
    end;
    

    然后你可以像这样在任何线程中直接调用它:

    TLog.LogMsg('some text message here');
    

    【讨论】:

    • 好的,我已经实现并且工作得很好。只是为了确保,我在主文件之外有这个类和所有其他类。是否需要在主文件中引用任何 Indy 单元?我已经添加了 IdSync,但我不确定。
    • 好吧,由于TLog.DoNotify() 访问Form1TLog 单元需要引用Form1 的单元。
    猜你喜欢
    • 1970-01-01
    • 2011-09-24
    • 1970-01-01
    • 1970-01-01
    • 2011-11-27
    • 2017-09-22
    • 2018-02-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多