Hi,
It's not as easy as it seems, because events occur asynchronously.
When Worker.ReportFeedback method is called, the function returns immediately and the thread continues with the next statement. Later, at an unpredictable time, the OnWorkFeedback event occurs.
Even, if worker thread has a higher priority or has a small task to do, the OnWorkFeedback and OnWorkProgress events may occur when the thread is terminated and the task is completely done.
To see it in real, place a TBackgroundWorker and a TListBox on a form and write the following event handlers:
- Code: Select all
procedure TForm1.FormCreate(Sender: TObject);
begin
BackgroundWorker1.Execute;
end;
procedure TForm1.BackgroundWorker1Work(Worker: TBackgroundWorker);
var
I: Integer;
begin
for I := 1 to 999 do
begin
Worker.Tag := I;
Worker.ReportFeedback(0, I);
end;
end;
procedure TForm1.BackgroundWorker1WorkFeedback(Worker: TBackgroundWorker;
FeedbackID, FeedbackValue: Integer);
begin
ListBox1.Items.Add(Format('%d - %d', [FeedbackValue, Worker.Tag]));
end;
In the above code, the thread sets the Tag property of Worker to the loop variable and raises the OnWorkFeedback event by setting its FeedbackValue to the loop variable. Later, the OnWorkFeedback event handler shows the FeedbackValue and Worker's Tag property on the ListBox.
If OnWorkFeedback event was occurred synchronously, the Worker's Tag property was equal to FeedbackValue. However, as you can see, in an asynchronous call the result is unpredictable. In our example, all the OnFeedbackEvents are occurred when the thread was terminated because the Worker.Tag has a value of 999 for all feedback values.
Of course we can use an Event object to synchronize the thread with the OnWorkFeedback event handler. For this purpose, we add
SyncObjs unit to the uses clause and declare an Event object. Here named SyncFeedback:
- Code: Select all
uses
SyncObjs;
var
SyncFeedback: TEvent;
The Event object must be created and released, so we use OnFormCreate and OnFormDestroy event for this purpose:
- Code: Select all
procedure TForm1.FormCreate(Sender: TObject);
begin
SyncFeedback := TEvent.Create(nil, True, True, '');
BackgroundWorker1.Execute;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeAndNil(SyncFeedback);
end;
Then, we have to modify our OnWork event handler, so that after calling ReportFeedback, the thread waits for OnWorkFeedback event to occur:
- Code: Select all
procedure TForm1.BackgroundWorker1Work(Worker: TBackgroundWorker);
var
I: Integer;
begin
for I := 1 to 999 do
begin
Worker.Tag := I;
SyncFeedback.ResetEvent; // reset the event, we are going to wait for it to be set
Worker.ReportFeedback(0, I);
// some code that do not affect the synchronization can be here
SyncFeedback.WaitFor(INFINITE); // wait for event to be set
end;
end;
Then, in OnWorkFeedback event handler, we signal the Event object, so that the thread can continue its work:
- Code: Select all
procedure TForm1.BackgroundWorker1WorkFeedback(Worker: TBackgroundWorker;
FeedbackID, FeedbackValue: Integer);
begin
ListBox1.Items.Add(Format('%d - %d', [FeedbackValue, Worker.Tag]));
SyncFeedback.SetEvent; // set the event, so that thread continues
end;
Here is the complete code for the synchronization:
- Code: Select all
uses
SyncObjs;
var
SyncFeedback: TEvent;
procedure TForm1.FormCreate(Sender: TObject);
begin
SyncFeedback := TEvent.Create(nil, True, True, '');
BackgroundWorker1.Execute;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeAndNil(SyncFeedback);
end;
procedure TForm1.BackgroundWorker1Work(Worker: TBackgroundWorker);
var
I: Integer;
begin
for I := 1 to 99 do
begin
Worker.Tag := I;
SyncFeedback.ResetEvent; // reset the event, we are going to wait for it to be set
Worker.ReportFeedback(0, I);
// some code that do not affect the synchronization can be here
SyncFeedback.WaitFor(INFINITE); // wait for event to be set
end;
end;
procedure TForm1.BackgroundWorker1WorkFeedback(Worker: TBackgroundWorker;
FeedbackID, FeedbackValue: Integer);
begin
ListBox1.Items.Add(Format('%d - %d', [FeedbackValue, Worker.Tag]));
SyncFeedback.SetEvent; // set the event, so that thread continues
end;
By running the new code, you will see Worker.Tag value is always equal to FeedbackValue, as expected.
When synchronization is involved, you can type cast a pointer or object to integer and pass it as FeedbackValue to the event handler.
In the next release of TBackgroundWorker, I will add some synchronization features to make these sort of tasks easier.Besides using synchronization, there is other solution for sending strings, objects, or records to the main VCL thread. In this case, the worker thread sends a copy of string, object, or record to the OnWorkFeedback event handler. Later, the OnWorkFeedback event handler must release the allocated memory. Because the worker thread does not need to wait for the main VCL thread, this solution is preferred for threads with heavy works to do.