问题
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 whenTThread.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:
your thread code is in a console app that does not have a
TApplication
object, which hooks intoWakeMainThread
, or the app does not have a message loop that processes the wake-up message.your code is in a GUI app, and something in the main UI thread is blocking it from processing messages in a timely manner.
there are no pending messages in the main message queue when
Synchronize()
is called, and for whatever reason no function has been assigned toWakeMainThread
, so the main UI thread does not receiveSynchronize()
'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