Delphi:通过TThread验证DataSnap连接

时间:2015-09-01 12:23:02

标签: multithreading delphi connection datasnap

我们有一个应用程序,用户可以与我们交谈,它工作正常,他创建一个新的对话,我们聊天,这没关系。但是,在开始聊天之前,他需要连接到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时所做的工作,如果它的线程或它将冻结独立不。

尝试连接时有没有办法冻结?

这是我的第一个主题,也许我只是误解了事情而且线程不是很有效,但是,如果不是,那么我需要知道,或者至少理解我在做什么错了。

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

2 个答案:

答案 0 :(得分:0)

有两种选择:

  1. public MyCard1(Context context) { super(context, R.layout.home_card_inner_content1); } @Override public void setupInnerViewElements(ViewGroup parent, View view) { TextView mTitleTextView1 = (TextView) parent.findViewById(R.id.home_card_main_inner_title1); final TextView mSecondaryTitleTextView1 = (TextView) parent.findViewById(R.id.home_card_main_inner_subtitle1); new CountDownTimer(86500000, 1000) { // adjust the milli seconds here public void onTick(long millisUntilFinished) { day1 = TimeUnit.MILLISECONDS.toDays(millisUntilFinished); hour1 = TimeUnit.MILLISECONDS.toHours(millisUntilFinished)- TimeUnit.DAYS.toHours(TimeUnit.MILLISECONDS.toDays(millisUntilFinished)); min1 = TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millisUntilFinished)); sec1 = TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished)); mSecondaryTitleTextView1.setText(day1 + " day, " + hour1 + " hour, " + min1 + " minute, " + sec1 + " second"); } public void onFinish() {mSecondaryTitleTextView1.setText("done!");} }.start(); if (mTitleTextView1 != null) mTitleTextView1.setText(mainTitle1); if (mSecondaryTitleTextView1 != null) mSecondaryTitleTextView1.setText(secondaryTitle1); } } 的{​​{1}}事件在创建计时器的线程中执行时,您可以考虑在主线程外创建实例
  2. 您可以考虑使用OnTimer类实例
  3. 以下内容适用于#2。

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

    完成后,可以使用Execute选择性地通知主线程。

    TThread.Sleep(FInterval);

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

答案 1 :(得分:0)

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

在行之间进行读取,看起来您已连接到本地SQL服务器。访问频率很低,导致连接断开,因此您已经设置了一个计时器,每隔5分钟检查一次,并在必要时重新建立连接。

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

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

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

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

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

主程序和线程都将封装在关键部分内对MySQLConnection的任何调用,尝试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的click事件中,我创建了一个TMyThread实例(在你的代码中,这将由timer事件处理):

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包含线程。执行方法是一次火,完成工作。 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连接到连接到连接到MySQLConnection的TSQLDataSet的TDataSetProvider,则打开TClientDataSet将必须封装在此FLock中尝试最后:

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

其中CDS是TClientDataSet。

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