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

孤街浪徒 提交于 2021-02-08 12:14:42

问题


I have a unit and form which uses a worker thread to query a database. After each query is executed a method is called using Synchronize to update the UI.

It seems that whenever a call to the methods using Synchronize() is made, the process stops until I shake the mouse or interact with the keyboard.

Below is the basics of what I am doing. This example is complete other than that I did not provide a database connection or queries for a real database- I am hoping it will be enough to see the code.

A theory is that limiting the synchronize calls to one call at the end to update the entire UI will make this work, though I am not 100% on if or why this would help - something to do with not switching what the CPU is doing too often perhaps? It does not seem possible to have only one Synchronize given how I must iterate through different query results to populate the TListViews. I do want to keep using list views as shown and not change the method for displaying rows of data.

  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.

回答1:


Synchronize() puts the method pointer into a queue and then waits for the main UI thread to process that queue. That processing happens inside of the RTL's CheckSynchronize() function.

As I stated in comments to 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.

Normally, Synchronize() "wakes up" the main UI thread, in case there are no pending messages. There is a function pointer in the RTL named WakeMainThread, which Synchronize() calls if assigned. This lets the main UI thread post a message to itself so it can call CheckSynchronize() sooner rather than later.

The behavior you describe can occur under one of these possible scenarios:

  1. your thread code is in a console app that does not have a TApplication object, which hooks into WakeMainThread, or the app does not have a message loop that processes the wake-up message.

  2. your code is in a GUI app, and something in the main UI thread is blocking it from processing messages in a timely manner.

  3. there are no pending messages in the main message queue when Synchronize() is called, and for whatever reason no function has been assigned to WakeMainThread, so the main UI thread does not receive Synchronize()'s wake-up request, and thus does not process the synchronize queue until a new message receives eventually, like mouse/keyboard activity.

You have not provided any details about your application or its setup, so there is no way to diagnose which of these possibilities is actually affecting your project.



来源:https://stackoverflow.com/questions/38854528/method-called-on-thread-using-synchronize-does-not-return-without-moving-the-mou

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!