【问题标题】:Hooking into message loop of dbx DataSnap user session连接到 dbx DataSnap 用户会话的消息循环
【发布时间】:2012-02-19 02:00:41
【问题描述】:

有没有办法挂钩到 dbx 用户会话的 WndProc?

背景: dbx DataSnap 使用 Indy 组件进行 TCP 通信。在最简单的形式中,DataSnap 服务器是一个接受连接的 Indy TCP 服务器。建立连接后,Indy 会为该连接创建一个线程来处理该连接的所有请求。

这些用户连接中的每一个都会消耗资源。对于具有几百个同时连接的服务器,这些资源可能很昂贵。许多资源可以被池化,但我不想总是在每次需要时都获取和释放资源。

相反,我想实现一个空闲计时器。在线程完成资源后,计时器将启动。如果线程在计时器结束之前访问资源,则资源仍将“分配”给该线程。但是,如果在下一次访问之前计时器已过,则资源将被释放回池中。下次线程需要该资源时,将从池中获取另一个资源。

我还没有找到方法来做到这一点。我试过使用 SetTimer 但我的计时器回调永远不会触发。我认为这是因为 Indy 的线程 WndProc 没有调度 WM_TIMER。我无法控制该线程的“执行循环”,因此我无法轻松检查是否已发出事件信号。事实上,除非线程正在处理用户请求,否则我的这个线程的代码都不会执行。事实上,我希望代码在任何用户请求之外执行。

我们将同样感谢原始问题的解决方案或替代方法的建议。

【问题讨论】:

  • Remy Lebeau 在其他地方指出 Indy 的线程没有消息循环。我之前曾尝试创建一个消息循环并在用户线程中实现我自己的 WndProc,但我的 WndProc 从未收到消息。 SetTimer 和传统的 TTimer 都不适用于我的消息循环。 (除了线程安全问题,这些问题已经得到解决。)我在许多其他应用程序的许多线程中创建了消息循环。但绝不会在 DataSnap 或 Indy TCP 服务器中。
  • 我应该提到我正在使用 Delphi XE 和 DSTCP 传输。在另一个论坛中,Mat DeLong 建议使用 TDSSessionManager.Instance.AddSessionEvent。不幸的是,TDSSessionManager 不适用于 Delphi XE 中的 TCP 连接。 XE2 中修复了几个严重的问题。
  • 我相信您在这里做任何实际事情的机会将需要 XE2,它具有一些新功能,可以减少您甚至不必担心像这个空闲计时器这样的低级黑客。
  • XE2 暂时不适合我们。我真的希望有人能提出可以在 XE 中完成的建议。我不是在寻找其他人来创建解决方案。除了我已经尝试过的以外,我只需要一些建议。我在思考这个问题时遇到了问题。

标签: delphi delphi-xe datasnap indy10


【解决方案1】:

我们尝试使用 TCP 连接(没有 HTTP 传输,所以没有 SessionManager)实现跨用户线程共享资源,但遇到了各种各样的问题。最后我们放弃了使用单独的用户线程(设置LifeCycle := TDSLifeCycle.Server)并在ServerContainerUnit中创建了我们自己的FResourcePoolFUserList(都是TThreadList)。实施只用了1天,效果很好。

这是我们所做工作的简化版本:

TResource = class
  SomeResource: TSomeType;
  UserCount: Integer;
  LastSeen: TDateTime;
end;

当用户连接时,我们会检查 FResourcePool 以获取用户需要的 TResource。如果存在,我们增加资源的UserCount 属性。当用户完成后,我们减少UserCount 属性并设置LastSeen。我们有一个TTimer,它每 60 秒触发一次,它会释放 UserCount = 0LastSeen 大于 60 秒的任何资源。

FUserList 非常相似。如果几个小时没有看到用户,我们假设他们的连接被切断(因为如果用户空闲 90 分钟,我们的客户端应用程序会自动断开连接)所以我们以编程方式断开服务器端的用户,这也减少了他们对每种资源的使用。当然,这意味着我们必须自己创建一个会话变量(例如,CreateGUID();)并在客户端第一次连接时将其传递给客户端。客户端通过每个请求将会话 ID 传递回服务器,因此我们知道哪个 FUserList 记录是他们的。虽然这是使用用户线程的一个缺点,但它很容易管理。

【讨论】:

  • 感谢您的建议。我没有标记为答案,因为它没有回答我的问题。但我非常感谢您的反馈。在生产中,我们已经看到 160 多个同时用户,其中相当多的用户在任何特定时刻都处于活跃状态。 “每个用户一个线程”模型对我们来说非常有效,这不是我想失去的东西。当 DataSnap 已经做得很好时,我也不想实现我自己的线程处理。
【解决方案2】:

詹姆斯 L 可能已经成功了。由于 Indy 线程没有消息循环,因此您必须依赖另一种机制 - 例如只读线程本地属性(例如 UserCount 和/或 LastSem in his'示例) - 并使用服务器的 主线程 运行 TTimer 以根据某些规则释放资源。

编辑:另一个想法是创建一个通用数据结构(下面的示例),每次线程完成其工作时都会更新它。

警告:仅凭头脑编码...它可能无法编译... ;-)

例子:

TThreadStatus = (tsDoingMyJob, tsFinished);

TThreadStatusInfo = class
private
  fTStatus : TThreadStatus;
  fDTFinished : TDateTime;
  procedure SetThreadStatus(value: TThreadStatus);
public
  property ThreadStatus: TThreadStatus read fTStatus write SetStatus;
  property FinishedTime: TDateTime read fDTFinished;
  procedure FinishJob ;
  procedure DoJob;
end

procedure TThreadStatusInfo.SetThreadStatus(value : TThreadStatus)
begin
  fTStatus = value;
  case fTStatus of 
    tsDoingMyJob :
       fDTFinished = TDateTime(0);
    tsFinished:
       fDTFinished = Now;
  end;
end;

procedure TThreadStatusInfo.FinishJob;
begin
  ThreadStatus := tsFinished;
end;

procedure TThreadStatusInfo.DoJob;
begin
  ThreadStatus := tsDoingMyJob;
end;

把它放在一个列表中(任何你喜欢的列表类),并确保每个线程都是关联的 在该列表中有一个索引。仅当您不使用时才从列表中删除项目 线程数不再(缩小列表)。创建新话题时添加项目 (例如,您有 4 个线程,现在需要第 5 个,您在 主线程 上创建一个新项目。

由于每个线程在链表上都有一个索引,所以不需要封装这个写( 呼吁 T 在 TCriticalSection 上。

您可以轻松阅读此列表,在主线程上使用 TTimer 进行检查 每个线程的状态。既然你有每个线程的完成时间 你可以计算超时时间。

【讨论】:

  • 我很抱歉投反对票,但我不确定你为什么发布这个答案。您本可以评论或支持 James L 的回答。
  • 我计划添加另一个想法,但我得到了一个优先级转移,这让我做了一个不完整的答案。
  • 你是 SO 上最善良的反对者之一......有些人似乎只是为了运动而反对......
  • 删除反对票,因为您发布了其他想法。一有空我就去看看!
  • 问题是,即使主线程识别出空闲状态,需要释放的对象也必须由创建它的线程释放。它是一个 COM 对象。尽管我可以在线程之间来回编组对象,但这种开销确实违背了我希望实现的目的。我的主线程仍然需要一种方法来向拥有线程发出信号以释放对象。如果我能解决这个挑战,我可以在子线程中做所有事情。感谢您的建议。
猜你喜欢
  • 2015-07-21
  • 1970-01-01
  • 2015-05-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-13
  • 1970-01-01
  • 2022-01-25
相关资源
最近更新 更多