2015-09-01 29 views
0

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

我的主题:

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

您正在调用线程中的“同步(TryConnect)”。这意味着你正在连接主线程。 –

+0

好吧,我明白你在说什么,但我认为'Synchronize'会使它与应用程序平行,就像它们两个同时运行而不会相互干扰一样。不是吗? –

+1

不,“Synchronize”表示代码将在主线程中执行。请参见[TThread.Synchronize](http://docwiki.embarcadero.com/Libraries/en/System.Classes.TThread.Synchronize)。 –

回答

0

有两种选择:

  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; 

不要synchonize你想在一个线程中执行的代码:Delphi中syncronized块调用线程总是执行。

+0

什么应该是一个“真正的”线程?我不认为我得到你想说的话。 –

+0

@GeovaneS是一个真正的TThread类,它可以更好地控制Execute方法:相关代码可以在主线程中以“同步”方式执行或不执行 – fantaghirocco

+0

就是这样。就像我期望我的工作一样。但是做不到。我只是不知道那里发生了什么,比如说,'FWaitEvent.WaitFor(FInterval)'是什么?为什么有必要有一个'while not Terminated do'循环。 –

0

我本来希望发表评论而不是回答,但缺乏声望点;阅读以下内容值得考虑。

在行之间读取,它看起来像有一个连接到本地SQL服务器。访问很少会导致连接中断,因此您已经设置了一个计时器来检查每5分钟,并在必要时重新建立连接。

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

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

同步是允许线程,而不必担心两个或多个线程访问同一对象在同一时间与主螺纹相互作用的最简单的装置。

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

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

两个主程序和线程将封装的关键部分内的MySqlConnection任何呼叫尝试finally块:

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

凡羊群是TCriticalSection对象。

任何线程试图同时已经被其他收购获得的羊群,将被阻止,直到羊群被释放。这意味着只有当工作线程已经尝试连接时用户试图访问MySQLConnection时,主线程才会被阻止。

更新:

为了让您一开始,以下是由两个单位一个简单的程序; Unit1包含主窗体(创建新应用程序时提供的内容)。第二个单元Unit2包含一个线程。我已经这样做了,因为你的线程似乎是在一个单独的单元。

我添加了一个按钮和一个关键部分TForm1(添加System.SyncObjs的用途条款)。在Button1的Click事件创建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包含线程。执行的方法是一次火灾,并完成努力。 1单元添加到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底部的线程状态窗口现在列出了此额外线程。当你再次按F9时,这个新线程消失。

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

在主线程中,无论何处访问MySQLConnection的方法,还需要将这些封装在FLock try finally块中。例如,如果您有连接到连接到连接到您的MySqlConnection,然后在打开的TClientdataSet放置一个TSQLDataSet一个TDataSetProvider一个TClientDataSet将不得不涌向这个包裹内尝试最后:

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

如果CDS是将TClientDataSet。

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

+0

感谢你简单地解释了'TCriticalSection'是如何工作的,我发现当它们两个同时连接时,你有一个很好的观点。我猜我需要在我的线程的私有部分声明'FLOCK',它不能只是'Execute'过程中的局部变量吗?如果不是,为什么? –

+0

@GeovaneSilveira如果我被允许,每个试图访问共享资源的进程都必须使用'FLock'。一种选择是在表单的“私人”部分声明它。共享方法也必须采用这种形式。为了让线程在不需要表单实例的情况下可以访问resorce,可以考虑使用'class'变量和方法。 – fantaghirocco

+0

@GeovaneSilveira:正如fantaghirocco所指出的那样,FLock必须可以在主窗体和工作线程中访问相关的方法。 FLock的最佳场所是主要形式。如果FLock在线程中,则线程创建之前它不会存在,并且在线程被销毁时将不再存在。这将使主要形式中需要使用关键部分的方法变得困难。如果线程被定义在与主窗体相同的单元中,那么它将能够访问窗体中声明的私有变量。 – CAnder