在不移动鼠标的情况下使用 Synchronize 在线程上调用的方法不会 return

Method called on thread using Synchronize does not return without moving the mouse

我有一个使用工作线程查询数据库的单元和表单。在执行每个查询后,将使用 Synchronize 调用一个方法来更新 UI。

似乎每当调用使用 Synchronize() 的方法时,进程都会停止,直到我摇动鼠标或与键盘交互。

以下是我正在做的事情的基础知识。除了我没有提供数据库连接或对真实数据库的查询之外,这个示例是完整的 - 我希望它足以让您看到代码。

一种理论是,将同步调用限制为最后一次调用以更新整个 UI 将使这项工作有效,尽管我不是 100% 确定这是否或为什么会有所帮助 - 要做的事情也许不切换 CPU 经常做的事情?考虑到我必须如何遍历不同的查询结果来填充 TListView,似乎不可能只有一个 Synchronize。我确实想继续使用如图所示的列表视图,而不更改显示数据行的方法。

  unit ExampleCode;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, ExtCtrls, ADODB;

type
  TRefreshThread = class(TThread)
  public
  Procedure Execute; override;
  procedure UpdateLabel1;
  procedure UpdateLabel2;
  procedure UpdateLabel3;
  procedure UpdateLabel4;
  procedure populateListView1;
  procedure populatelistView2;
  procedure populatelistview3;
  procedure OnThreadTerminate(Sender : Tobject);
  constructor Create;
  end;

  TForm1 = class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    ListView1: TListView;
    ListView2: TListView;
    ListView3: TListView;
    Timer1: TTimer;
    procedure FormCreate(Sender: TObject);
   procedure Timer1Timer(Sender: TObject);
  end;

  var
  Form1: TForm1;
  RefreshThread : TRefreshThread;
  Query : TADOQuery;
  someDatabase : TADOConnection;

  implementation

   {$R *.dfm}

procedure TRefreshThread.UpdateLabel1;
begin

Form1.Label1.Caption := Query.FieldByName('Field1').AsString;

end;

procedure TRefreshThread.UpdateLabel2;
begin

Form1.Label1.Caption := Query.FieldByName('Field2').AsString;

end;


constructor TRefreshThread.Create;
begin

FreeOnTerminate := true;
OnTerminate := OnThreadTerminate;
inherited Create(false);

end;

procedure TRefreshThread.OnThreadTerminate(sender : TObject);
begin

sender := nil;

end;

procedure TRefreshThread.UpdateLabel3;
begin

Form1.Label1.Caption := Query.FieldByName('Field3').AsString;

end;

procedure TRefreshThread.UpdateLabel4;
begin

Form1.Label1.Caption := Query.FieldByName('Field4').AsString;

end;


procedure TRefreshThread.populateListView1;
var
Item1 : TListItem;
begin

Item1 := Form1.ListView2.Items.Add;
Item1.Caption := Query.FieldByName('Field5').AsString;
Item1.SubItems.Add(Query.FieldByName('Field6').AsString);
Item1.SubItems.Add(Query.FieldByName('Field7').AsString);

end;


procedure TRefreshThread.populateListView2;
var
Item1 : TListItem;
begin

Item1 := Form1.ListView2.Items.Add;
Item1.Caption := Query.FieldByName('Field8').AsString;
Item1.SubItems.Add(Query.FieldByName('Field9').AsString);
Item1.SubItems.Add(Query.FieldByName('Field10').AsString);


end;


procedure TRefreshThread.populateListView3;
var
Item1 : TListItem;
begin

Item1 := Form1.ListView2.Items.Add;
Item1.Caption := Query.FieldByName('Field11').AsString;
Item1.SubItems.Add(Query.FieldByName('Field12').AsString);
Item1.SubItems.Add(Query.FieldByName('Field13').AsString);

end;


procedure TRefreshThread.Execute;

begin


Query.Create(nil);

Query.Connection := SomeDatabase;

Query.SQL.Add('select FIELD1 from TABLE');

Query.Active := true;

Synchronize(UpdateLabel1);

Query.Close;
Query.SQL.Clear;

Query.SQL.Add('select FIELD2 from TABLE');

Query.Active := true;

Synchronize(UpdateLabel2);

Query.Close;
Query.SQL.Clear;


Query.SQL.Add('select FIELD3 from TABLE');

Query.Active := true;

Synchronize(UpdateLabel3);

Query.Close;
Query.SQL.Clear;

Query.SQL.Add('select FIELD4 from TABLE');

Query.Active := true;

Synchronize(UpdateLabel4);

Query.Close;
   Query.SQL.Clear;


   Query.SQL.Add('select FIELD5, Field6, Field7 from TABLE');

   Query.Active := true;

   try
   while not Query.Eof do
   begin
       Synchronize(PopulateListView1);
       Query.Next;
   end;
   finally
   Query.Close;
   Query.SQL.Clear;
   end;


   Query.SQL.Add('select FIELD8, Field9, Field10 from TABLE');

   Query.Active := true;


   try
   while not Query.Eof do
   begin
   Synchronize(PopulateListView2);
   Query.Next;
   end;
   finally
   Query.Close;
   Query.SQL.Clear;
   end;


Query.SQL.Add('select FIELD11, Field12, Field13 from TABLE');

Query.Active := true;


try
  while not Query.Eof do
begin
Synchronize(PopulateListView3);
Query.Next;
end;
finally
Query.Close;
Query.SQL.Clear;
end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin

Timer1.Create(nil);
Timer1.Interval := 1000;
RefreshThread := TRefreshThread.Create();

end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin

if RefreshThread = nil then
RefreshThread := RefreshThread.Create();

end;

end.

Synchronize() 将方法指针放入队列,然后等待主 UI 线程处理该队列。该处理发生在 RTL 的 CheckSynchronize() 函数内部。

正如我在 the question you linked to 的评论中所说:

CheckSynchronize() is also called when TThread.WaitFor() is called in the context of the main thread, and whenever the main thread message loop goes idle after processing all of the pending messages in the message queue.

通常,Synchronize()“唤醒”主UI线程,以防没有未决消息。 RTL 中有一个名为 WakeMainThread 的函数指针,如果分配,Synchronize() 将调用它。这让主 UI 线程 post 向自己发送消息,以便它可以尽快调用 CheckSynchronize()

您描述的行为可能发生在以下其中一种可能的情况下:

  1. 您的线程代码位于没有 TApplication 对象的控制台应用程序中,该对象挂接到 WakeMainThread,或者该应用程序没有处理的消息循环唤醒消息。

  2. 您的代码在 GUI 应用程序中,UI 主线程中的某些内容阻止它及时处理消息。

  3. 调用Synchronize()时主消息队列中没有待处理的消息,并且由于某种原因没有为WakeMainThread分配函数,所以主UI 线程没有收到 Synchronize() 的唤醒请求,因此在最终收到新消息之前不会处理同步队列,如 mouse/keyboard activity.

您没有提供有关您的应用程序或其设置的任何详细信息,因此无法诊断这些可能性中的哪一种实际上影响了您的项目。