我们有一个应用程序,用户可以在其中与我们交谈,它运行良好,他创建了一个新的对话,我们聊天,这是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,这将冻结独立,如果它是线程或不是。
是否有任何方法在尝试连接时不冻结?
这是我的第一个线程,也许我只是误解了事情,这不是线程的工作方式,但是,如果不是,那么我需要知道,或者至少明白我做错了什么。
编辑:我正在做的测试是,我在没有启动服务器的情况下启动应用程序,所以它会尝试连接不成功,我的数据模块也不会连接。有两个选项:
- 当
TTimer
的OnTimer
事件在创建计时器的线程中执行时,您可以考虑在主线程之外创建实例 你可以考虑使用
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的访问都受关键区保护)的一个附带好处是,它将防止在用户查询中间关闭连接。