使用 AsyncCalls 很容易做到这一点。假设您的非线程代码如下所示(忽略所有错误处理):
Query := 'select (*) as DBCount from...';
ExecuteSQL(SqlConnection,Query);
SqlResult := GetSqlResult(SqlConnection);
SqlRow := GetSqlRow(SqlResult);
MyLabel.Text := SqlRow[0];
...Go on to do other things...
第二行阻塞的地方(等待服务器回复)。您的新代码如下所示:
uses AsyncCalls; //added to your existing uses statement
...
procedure DoesSomething();
var Thread: TAsyncCall; //your interface to AsyncCalls
procedure AsyncSqlCall(); //this is a LOCAL procedure
Query := 'select (*) as DBCount from...';
ExecuteSQL(SqlConnection,Query);
SqlResult := GetSqlResult(SqlConnection);
SqlRow := GetSqlRow(SqlResult);
EnterMainThread;
try
Assert(GetCurrentThreadId = MainThreadId);
MyLabel.Text := SqlRow[0];
finally
LeaveMainThread;
end;
begin //this begins proc DoSomething()
...
Thread := LocalAsyncCall(@AsyncSqlCall);
...Go on to do other things...
end;
我们所做的只是将阻塞的 SQL 调用放在本地 proc 中,并告诉 AsyncCalls 在另一个线程中执行它,而主线程继续执行。唯一棘手的部分是使用 VCL,它不是线程安全的。所以我让那行安全地在主线程中运行。
如果在某些时候您需要确保 Async 线程已完成,您将执行此行以阻塞主线程,直到 AsyncSqlCall 终止:
Thread.sync;
这里真正的好处是 AsyncCalls 处理所有关于创建线程池、创建线程等的事情。虽然在这个例子中没有显示,你可以将变量传递给你的线程,并返回一个值。您不必使用本地 proc,但这样做可以访问所有本地变量。您可以将所有这些设为全局,然后在一个例程中启动 Async 线程,并在另一个例程中测试其是否完成。
限制:
您的异步线程不得接触(读取或写入)除其自身变量之外的任何内容,并且您的主线程不得在异步线程运行时接触它们。你必须这样编码。没有什么能阻止你制造完全的混乱。在上面的例子中,你的主线程不能接触 Query、SqlConnection、SqlResult 和 SqlRow。如果您的代码的任何部分在调用 Thread.sync 之前使用了这些变量之一,那么您的代码将可以工作——但会在您从未预料到的奇怪地方抛出异常。所以保持简单。
您的异步线程不得使用 VCL。上面的示例显示了安全绕过此限制的几种方法之一。
最后:
AsyncCalls 不是一个完整的多线程框架。这只是一种异步调用过程和函数的方式(即无需等待)。不要试图把它推得太远——我的意思是,不要试图让它成为一个完全多任务程序的基础。