Delphi:如何在本地创建和使用线程?

5

我的数据库在VPS上,我需要从我的表中获得一些查询

由于从服务器获取查询需要很长时间(取决于互联网速度!),因此我想使用线程来获取查询

现在我创建一个线程并获取查询,然后发送结果到我的表单,并通过发送和处理消息来处理

我想知道是否可以在本地创建和使用线程?!?

我的意思是:

procedure Requery;
var
 ...
begin
 Create Thread;
 ...

 Pass my Query Component to Thread
 ...

 Getting Query in Thread;
 ...

 Terminate and Free Thread
 ...

 Do next jobs with Query;
 ...

end;

主要部分是最后一部分(接下来要做的工作......),我不想在消息处理程序中使用查询结果,而是想在同一个过程中使用它们并在线程作业之后使用。

这是否可能?!

我认为在Delphi TThread类中不可能实现这一点,我应该使用其他线程技术...

  • 我正在使用Delphi XE6

TThread只是一个用于构建线程的基本包装器 - 查看源代码即可。你缺少什么? - Sir Rufo
你是什么意思错过的?! - Mahmoud_Mehri
你的意思是什么?我认为使用Delphi的TThread类不可能实现这个。 - Sir Rufo
你也可以使用我针对线程的更高级封装器,即 .net BackgroundWorker 的端口 - Sir Rufo
3个回答

14

你描述的不是使用线程的最佳方式。调用代码会被阻塞,直到线程完成。这完全抵消了并行运行代码的好处。你可以直接执行查询而不必使用线程:

procedure Requery;
var
  ...
begin
  ...
  // run query
  // do next jobs with query
  ...
end;

话虽如此,既然您正在使用XE6,您可以使用TThread.CreateAnonymousThread()方法创建一个“本地”线程,指定一个匿名过程来“捕获”您想要它处理的变量,例如:

procedure Requery;
var
  Event: TEvent;
  H: THandle;
begin
  Event := TEvent.Create;
  try
    TThread.CreateAnonymousThread(
      procedure
      begin
        try
          // run query in thread
        finally
          Event.SetEvent;
        end;
      end
    ).Start;
    H := Event.Handle;
    while MsgWaitForMultipleObjects(1, H, False, INFINITE, QS_ALLINPUT) = (WAIT_OBJECT_0+1) do
      Application.ProcessMessages;
  finally
    Event.Free;
  end;

  // Do next jobs with query
  ...
end;

或者:

procedure Requery;
var
  Thread: TThread;
  H: THandle;
begin
  Thread := TThread.CreateAnonymousThread(
    procedure
    begin
      // run query in thread
    end
  );
  try
    Thread.FreeOnTerminate := False;
    H := Thread.Handle;
    Thread.Start;
    while MsgWaitForMultipleObjects(1, H, False, INFINITE, QS_ALLINPUT) = (WAIT_OBJECT_0+1) do
      Application.ProcessMessages;
  finally
    Thread.Free;
  end;

  // Do next jobs with query
  ...
end;

然而,当你让线程在后台运行而你做其他事情时,线程会更有用,并且当线程完成工作时再进行操作。例如:

procedure TMyForm.Requery;
var
  Thread: TThread;
begin
  Thread := TThread.CreateAnonymousThread(
    procedure
    begin
      // run query in thread
    end
  );
  Thread.OnTerminate := QueryFinished;
  Thread.Start;
end;

procedure TMyForm.QueryFinished(Sender: TObject);
begin
  if TThread(Sender).FatalException <> nil then
  begin
    // something went wrong
    Exit;
  end;
  // Do next jobs with query
end;

在 Delphi Xe8 中,使用参数 Event.Handle 的 MsgWaitForMultipleObjects 会触发错误“无法将常量对象作为 var 参数传递”。你是否有更新版本/选项?谢谢。 - That Marc
请注意,最后一个示例中最新过程的末尾缺少“;”,并且“begin”拼写错误(被写成了“being”)。* - That Marc
@ThatMarc 我没有看到任何缺少的 ;。在作为参数传递的匿名过程的结尾不使用 ;。至于编译器错误,我已经更新了示例。 - Remy Lebeau
我的意思是在“procedure TMyForm.QueryFinished(Sender: TObject)”之后,在begin之前。当然,IDE会立即报告这个错误,但只是为了提供一个示例。没有恶意。 :) 感谢您提供更新的示例! - That Marc
@ThatMarc 已修复 - Remy Lebeau

0

我认为这种方式使用线程不是一个好主意,但答案是肯定的。你可以这样做。

procedure LocalThread;
var
  LThread: TCustomThread; //Your thread class
  LThreadResult: xxxxxxx//Your result type
begin
  LThread := TCustomThread.Create(True);
  try
    //Assign your properties

    LThread.Start;

    //Option A: blocking
    LThread.WaitFor;

    //Option B: non blocking
    while not LThread.Finished do
    begin
      Sleep(xx);
      //Some progress here ??
    end;

    //Here query your thread for your result property
    LThreadResult := LThread.MyResultProperty;
  finally
    LThread.Free;
  end

  //Do next jobs with LThreadResult
end;

谢谢,但是这种方式会导致程序在线程结束作业之前崩溃,所以最好不要使用线程!实际上,查询代码块应该在一个线程中执行,并且应用程序等待查询结束而不崩溃。 - Mahmoud_Mehri
5
马胡姆,你的程序并没有崩溃,而是卡住了。崩溃表示程序突然停止运行,而卡住则是指它仍在运行,但似乎陷入了无法继续的状态(“似乎”)。你可以通过处理消息来避免卡顿。在这种情况下,处理消息通常意味着设计不良,但如果你真的需要将这个任务发送到同一函数内的另一个线程中,那么你已经决定要使用不良的设计方式,所以请继续处理消息,等待线程完成。 - Rob Kennedy

0

是的,你可以这样做。

我会这样做:在你的表单中添加一个事件处理程序。
你需要在代码中链接事件处理程序,但这并不难。

像这样创建一个线程:

TMyEventHandler = procedure(Sender: TObject) of object;

type
  TMyThread = class(TThread)
  strict private
    FDoneEvent: TMyEvent;
    FDone: boolean;
    FQuery: TFDQuery;
    constructor Create(DoneEvent: TMyEventHandler; Query: TFDQuery);
    procedure Execute; override;
    function GetQuery: TFDQuery;
  public
    property Query read GetQuery;
  end;

  TForm1 = class(TForm)
    FDQuery1: TFDQuery;  //Do not connect the FDQuery1 to anything!
    DataSource1: TDataSource;
    DBGrid1: TDBGrid;
  private
    FOnThreadDone: TMyEventHandler;
    FMyThread: TMyThread;
    procedure DoThreadDone;
    procedure ThreadDone;
  public
    property OnThreadDone: TMyEventHandler read FOnThreadDone write FOnThreadDone;
  ....

implementation

constructor TMyThread.Create(DoneEvent: TMyEvent; Query: TFDQuery);
begin
  inherited Create(true);
  FDoneEvent:= DoneEvent;
  FQuery:= Query;
  Start;
end;

procedure TMyThread.Execute;
begin
  //Do whatever with the query
  //when done do:
  FDone:= true;
  Synchonize(Form1.DoThreadDone);
end;

function TMyThread.GetQuery: TFDQuery;
begin
  if not Done then Result:= nil else Result:= FQuery;
end;

procedure TForm1.DoThreadDone;
begin
  if Assigned(FOnThreadDone) then FOnThreadDone(Self);
end;

procedure TForm1.ThreadDone(Sender: TObject);
begin
  ShowMessage('Query is done');
  //Now you can display the result of the query, by wiring it
  //to a dataset.
  MyDataSource1.Dataset:= FMyThread.Query;
  FMyThread.Free;
end;

procedure TForm1.StartTheQuery;
begin
  OnThreadDone:= Self.ThreadDone;
  FMyThread:= TMyThread.Create(OnThreadDone, FDQuery1);
end;

现在查询将在后台运行,并在完成时向您的事件处理程序发出信号。同时,您可以进行所有的鼠标操作和用户交互,而不必担心。请注意,在线程使用它时,您不能使用FDQuery1,并且在线程运行时,您不能将FDQuery1连接到DataSource
将其保持未连接状态,并在ThreadDone事件处理程序中按照所示进行连接。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接