我们有一个应用程序,用户可以与我们交谈,它工作正常,他创建一个新的对话,我们聊天,这没关系。但是,在开始聊天之前,他需要连接到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时所做的工作,如果它的线程或它将冻结独立不。
尝试连接时有没有办法冻结?
这是我的第一个主题,也许我只是误解了事情而且线程不是很有效,但是,如果不是,那么我需要知道,或者至少理解我在做什么错了。
编辑:我正在做的测试是,我在没有启动服务器的情况下启动应用程序,因此它将尝试连接失败,我的数据模块也不会连接。答案 0 :(得分:0)
有两种选择:
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}}事件在创建计时器的线程中执行时,您可以考虑在主线程外创建实例OnTimer
类实例以下内容适用于#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访问权限),它将阻止在用户查询过程中关闭连接。