Delphi:通过 TThread 验证 DataSnap 连接

Delphi: Verify DataSnap connection via TThread

我们有一个应用程序,用户可以在其中与我们交谈,它工作正常,他创建一个新对话,我们聊天,没关系。但是,在开始聊天之前,他需要连接到 DataSnap 服务器,这就是我试图制作线程的地方。每隔5分钟,一个计时器会触发他的事件来创建线程并尝试连接服务器,如下所示:

我的帖子:

unit UThreadSnapConnection;

interface

uses
  System.Classes, System.SysUtils, Data.SqlExpr;

type
  TThreadSnapConnection = class(TThread)
  private
    FSnap: TSQLConnection;
    procedure TryToConnect;
  protected
    procedure Execute; override;
    constructor Create;
  public
    DMSnap: TSQLConnection;
    HostName: String;
    Port: String;
  end;

implementation

{ TThreadSnapConnection }

constructor TThreadSnapConnection.Create;
begin
  inherited Create(True);
  FreeOnTerminate := True;
end;

procedure TThreadSnapConnection.TryToConnect;
begin
  try
    FSnap := DMSnap.CloneConnection;
    FSnap.Connected := False;

    try
      FSnap.Connected := True;
    except

    end;

    if FSnap.Connected then
      DMSnap.Connected := True;
  finally
    FreeAndNil(FSnap);
  end;
end;

procedure TThreadSnapConnection.Execute;
begin
  Synchronize(TryToConnect);
end;

end.

我的计时器:

procedure TMyDataModuleSnap.TimerSnapTimer(Sender: TObject);
var
  MyThread: TThreadSnapConnection;
begin
  if not(MySQLConnection.Connected) then
  begin
    MyThread := TThreadSnapConnection.Create;

    MyThread.DMSnap   := MySQLConnection;
    MyThread.HostName := 'localhost';
    MyThread.Port     := '211';

    MyThread.Resume;
  end;
end;

我正在做的是尝试连接到服务器,如果它有效,那么它将使我的数据模块连接。

我的问题是,每次行

FSnap.Connected := True;

执行它会冻结应用程序 1~2 秒,而我创建线程的原因是不冻结。据我所知,它根本不应该打扰应用程序,所以我开始认为这可能是它在将 Connected 属性 设置为 True 时所做的工作,无论它是否是线程,它都会独立冻结。

有什么方法可以在尝试连接时不卡顿吗?

这是我的第一个主题,也许我只是误解了一些东西,这不是线程的工作原理,但是如果不是,那么我需要知道,或者至少了解我在做错什么。

编辑:我正在做的测试是,我在没有启动服务器的情况下启动应用程序,所以它会尝试连接不成功,我的数据模块也不会连接。

有两种选择:

  1. TTimerOnTimer事件是在创建定时器的线程中执行的,可以考虑在主线程外创建实例
  2. 您可以考虑使用 TThread class 实例

以下适用于#2。

在线程的 Execute 过程中使用 TEvent,您可以在执行下一个代码块之前等待一段 FInterval 时间。
Terminated 属性 设置为 True 时,此方法允许 Execute 方法在间隔计数期间也立即 return ,这与采用 TThread.Sleep(FInterval); 调用将在指定的时间内冻结线程本身。

可以选择在完成时使用 TNotifyEvent 通知主线程。

TMyThread = class(TThread)
  private
    FInterval: Integer;
    FTerminateEvent: TEvent;
  protected
    procedure Execute; override;
    procedure TerminatedSet; override;
  public
    OnEndJob: TNotifyEvent;
    constructor Create(Interval: Cardinal; CreateSuspended: Boolean);
    destructor Destroy; override;
end;

constructor TMyThread.Create(Interval: Cardinal; CreateSuspended: Boolean);
begin
  inherited Create(CreateSuspended);
  FInterval := Interval;
  FTerminateEvent := TEvent.Create(nil, False, False, '');
end;

destructor TMyThread.Destroy;
begin
  FTerminateEvent.Free;
  inherited;
end;

procedure TMyThread.TerminatedSet;
begin
  inherited;
  FTerminateEvent.SetEvent;
end

procedure TMyThread.Execute;
begin
  while not Terminated do begin
    //do your stuff

    //notify your connection to the main thread if you want
    if Assigned(OnEndJob) then
      Synchronize(procedure
          begin
            OnEndJob(Self);
          end);

    //wait fo some amount of time before continue the execution
    if wrSignaled = FterminateEvent.WaitFor(FInterval) then
      Break;
  end;
end;

不要同步要在线程中执行的代码:在Delphi中,同步块总是在调用线程中执行。

我宁愿post评论而不是回答,但缺少声望点;阅读以下内容时值得考虑的事情。

从字里行间看出,您似乎已连接到本地 SQL 服务器。访问很少导致连接断开,因此您设置了一个计时器,每 5 分钟检查一次,并在必要时重新建立连接。

这行得通,但您发现连接尝试会阻止程序执行,直到它建立为止,因此您想将此操作移至工作线程。

如 fantahirocco 所述,同步导致主程序线程中的代码 运行。我的理解是这段代码 运行s 在主线程中的所有消息都被处理之后,所以你可以通过让计时器 post 一条消息来实现相同的结果,并且相关的消息处理程序调用 TryToConnect (TryToConnect在这种情况下在主窗体中声明)。

同步是允许线程与主线程交互的最简单方法,而不必担心两个或多个线程同时访问同一个对象。

为了防止连接进程阻塞主程序线程,必须在 TThread 后代的 Execute 方法中设置 MySQLConnection Connected 属性(未封装在调用中同步)。

但是这样做会引入工作线程和主程序同时访问MySQLConnection的风险。为了防止这种情况,您需要引入关键部分或类似部分。如果不熟悉,请查看 RAD Studio 帮助中的 TCriticalSection;有一节关于临界区和一个例子。

然后主程序和线程都会将对 MySQLConnection 的任何调用封装在关键部分 try finally 块中:

FLock.Acquire;
try
  {code accessing MySQLConnection goes here}
finally
  FLock.Release;
end;

其中 FLock 是一个 TCriticalSection 对象。

任何试图获取 FLock 而已经被另一个线程获取的线程都将被阻塞,直到 FLock 被释放。这意味着只有在工作线程已经尝试连接时用户尝试访问 MySQLConnection 时,主线程才会被阻塞。

更新:

为了让您入门,下面是一个由两个单元组成的简单程序; Unit1 包含主窗体(创建新应用程序时显示的内容)。第二个单元 Unit2 包含一个线程。我已经这样做了,因为你的线程似乎在一个单独的单元中。

我已经向 TForm1 添加了一个按钮和一个关键部分(将 System.SyncObjs 添加到 uses 子句)。在 Button1 的点击事件中,我创建了一个 TMyThread 实例(在您的代码中,这将由计时器事件处理):

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    FLock: TCriticalSection;
  end;

var
  Form1: TForm1;

implementation

uses Unit2;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  TMyThread.Create;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FLock := TCriticalSection.Create;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FLock.Free;
end;

单元 2 包含线程。 execute 方法是一次性完成的。 Unit1 被添加到实现中的 uses 子句中,使代码能够访问 Form1 变量:

type
  TMyThread = class (TThread)
  protected
    procedure Execute; override;
  public
    constructor Create;
  end;

implementation

uses Unit1;


{ TMyThread }

constructor TMyThread.Create;
begin
  inherited Create (False);
end;

procedure TMyThread.Execute;
begin
  with Form1 do begin
    FLock.Acquire;
    try
      {access MySQLConnection methods here}
    finally
      FLock.Release;
    end;
  end;
end;

当您 运行 这个简单的程序并单击 Button1 时,将创建一个单独的线程和执行方法 运行,随后线程将被销毁。每次单击 Button1 时都会重复此过程。

如果在 MyThread := TMyThread.Create 行的 Unit1 中放置一个断点,并在 FLock.Acquire 行的 Unit2 中放置另一个断点, 运行 程序并单击 Button1,代码将停止在主线程中;左侧窗格中显示的线程 ID。 如果您单击 F9 继续执行程序,它将在 Unit2 断点处停止。您会注意到线程 ID 现在不同了,IDE 底部的线程状态 window 现在列出了这个额外的线程。当您再次按 F9 时,这个新线程就会消失。

这个程序什么都不做,但是你可以把你需要的任何 MySQLConnection 代码放在这个线程中 运行 我在 Try Finally 块中有评论的地方。

在主线程中,只要访问 MySQLConnection 的方法,您还需要将这些方法封装在 FLock try finally 块中。例如,如果您有一个 TClientDataSet 连接到一个 TDataSetProvider,该 TDataSetProvider 连接到一个连接到您的 MySQLConnection 的 TSQLDataSet,那么打开 TClientDataSet 将必须封装在这个 FLock Try Finally:

begin
  FLock.Acquire;
  try
    CDS.Open;
  finally
    FLock.Release;
  end;
end;

其中 CDS 是 TClientDataSet。

您打算在线程中 运行 的代码基本上关闭连接并重新打开它。关键部分的一个附带好处(如果配置正确,并且所有对 MySQLConnection 的访问都受关键部分保护),它将防止连接在用户查询中间被关闭。