Delphi:验证DataSnap连接通过TThread



我们有一个应用程序,用户可以在其中与我们交谈,它运行良好,他创建了一个新的对话,我们聊天,这是ok的。但是,在开始聊天之前,他需要连接到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;

execute会冻结应用程序1~2秒,我创建一个线程的原因是为了不冻结。只要我知道,它不应该打扰所有的应用程序,所以我开始认为也许这是它所做的工作,当设置连接属性为True,这将冻结独立,如果它是线程或不是。

是否有任何方法在尝试连接时不冻结?

这是我的第一个线程,也许我只是误解了事情,这不是线程的工作方式,但是,如果不是,那么我需要知道,或者至少明白我做错了什么。

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

有两个选项:

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

下列内容适用于#2。

在你的线程的Execute过程中使用TEvent,你可以在执行下一个代码块之前等待FInterval时间。当Terminated属性设置为True时,这种方法允许Execute方法在间隔计数期间立即返回,而采用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中,同步块总是在调用线程中执行。

我更愿意发表评论而不是回答,但缺乏信誉积分;

从字里行间看,看起来您已经连接到本地SQL服务器。访问不频繁导致连接断开,所以您设置了一个计时器,每5分钟检查一次,必要时重新建立连接。

此操作有效,但您发现连接尝试阻塞程序执行,直到它建立,因此您希望将此操作移动到工作线程。

如fantaghirocco所述,同步导致代码在主程序线程中运行。我的理解是,这段代码在主线程中的所有消息都被处理之后运行,因此您可以通过让计时器发布消息,以及相关的消息处理程序调用TryToConnect(在本例中,TryToConnect在主窗体中声明)来实现相同的结果。

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

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

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

主程序和线程都将在临界区try finally块中封装对MySQLConnection的任何调用:

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

其中FLock是一个TCriticalSection对象

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

更新:

为了让你开始,下面是一个由两个单元组成的简单程序;第1单元包含主表单(创建新应用程序时呈现的表单)。第二个单元,Unit2包含一个线程。我已经这样做了,因为你的线程似乎是在一个单独的单元。

我已经添加了一个按钮和一个关键部分的TForm1(添加系统。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;
Unit2包含线程。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时,将创建一个单独的线程并运行execute方法,然后销毁该线程。每次单击Button1时,都会重复此过程。

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

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

在主线程中,无论在哪里访问MySQLConnection的方法,你都需要将这些方法封装在一个FLock try finally块中。例如,如果你有一个TClientDataSet连接到一个TDataSetProvider连接到一个TSQLDataSet连接到你的MySQLConnection,那么打开TClientDataSet将不得不封装在这个FLock中Try Finally:

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

其中CDS为TClientDataSet。

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

最新更新