I am trying to call a function from another unit/class which would take some time in performing the task and would return a string value. I couldn't find a good reference something similar to C# async-await like simple approach in Delphi. Using Omni Thread library seems a good idea for me.
A simple example will be a great start for me.
Sample approach:
procedure TForm1.button1Click(Sender: TObject);
begin
// notify before starting the task
memo1.Lines.Add('calling a asynchronous function..');
// call to the function that takes some time and returns a string value
memo1.Lines.Add(GetMagicString);
// notify that the task has been completed
memo1.Lines.Add('Results fetched successfully.');
end;
Here, the function GetMagicString should process the result asynchronously. Once the result is obtained, only then the program should notify that the task has been completed. By the way, I'm using Delphi-XE.
Edit1:
Here is what I tried. But I am still unable to figure out the proper way to make the job done. The problem is how to return the value.
.....
private
ResultValue: IOmniFuture<string>;
.........
.....
function TForm1.FutureGet: string;
begin
Sleep(3000);
Result := 'my sample magic string response ' + IntToStr(Random(9999));
end;
procedure TForm1.FutureGetTerminated;
begin
// This code fired when the task is completed
memo1.Lines.Add(ResultValue.Value);
end;
function TForm1.GetMagicString: string;
begin
ResultValue := Parallel.Future<string>(FutureGet,
Parallel.TaskConfig.OnTerminated(FutureGetTerminated));
end;
Here, using Result := ResultValue.Value feezes the UI.
Edit2
I made changes as per the answer provided.
MainForm Code:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Unit2;
type
TForm1 = class(TForm)
memo1: TMemo;
button1: TButton;
procedure button1Click(Sender: TObject);
private
FOnStringReceived: TMyEvent;
procedure StringReceived(const AValue: string);
property OnStringReceived: TMyEvent read FOnStringReceived write FOnStringReceived;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.button1Click(Sender: TObject);
var
MyObject: TMyClass;
begin
// notify before starting the task
memo1.Lines.Add('calling a asynchronous function..');
// call to the function that takes some time and returns a string value
MyObject := TMyClass.Create;
OnStringReceived := StringReceived;
try
MyObject.GetMagicStringInBackground(OnStringReceived);
finally
MyObject.Free;
end;
end;
procedure TForm1.StringReceived(const AValue: string);
begin
memo1.Lines.Add(AValue);
// notify that the task has been completed
memo1.Lines.Add('Results fetched successfully.');
end;
end.
Other Unit Code:
unit Unit2;
interface
uses SysUtils, OtlTask, OtlParallel, OtlTaskControl;
type
TMyEvent = procedure(const aValue: string) of object;
type
TMyClass = class
private
FOnStringReceived: TMyEvent;
function GetMagicString: string;
public
procedure GetMagicStringInBackground(AEvent: TMyEvent);
end;
implementation
{ TMyClass }
function TMyClass.GetMagicString: string;
begin
Sleep(3000);
Result := 'my sample magic string response ' + IntToStr(Random(9999));
end;
procedure TMyClass.GetMagicStringInBackground(AEvent: TMyEvent);
var
theFunctionResult: string;
begin
Parallel.Async(
procedure
begin
theFunctionResult := GetMagicString;
end,
Parallel.TaskConfig.OnTerminated(
procedure (const task: IOmniTaskControl)
begin
if Assigned(AEvent) then
AEvent(theFunctionResult);
end)
);
end;
end.
Yes, the code works as expected. I just want to know if this is the best way of doing what I really want to perform.
You would normally use a future in a case where you want something executed in the background but still need the result in the same execution path. It basically lets you do something in the background while doing another thing in the main thread and you can then use the result of the background thread.
What you need to use is the Async abstraction that TLama linked to:
In your case it would be:
procedure TForm1.DoSomething;
var
theFunctionResult: string;
begin
memo1.Lines.Add('calling a asynchronous function..');
Parallel.Async(
procedure
begin
// executed in background thread
theFunctionResult := GetMagicString;
end,
procedure
begin
// executed in main thread after the async has finished
memo1.Lines.Add(theFunctionResult);
// notify that the task has been completed
memo1.Lines.Add('Results fetched successfully.');
end
);
end;
This is a bit messy but you should get the idea. You need to make sure that your async code is completed before you destroy the form that owns this code (TForm1).
If you want to try and setup a system that will call an event when the code completes then you can do something like this:
type
TMyEvent = procedure(const aValue: string) of object;
procedure GetMagicStringInBackground(AEvent: TMyEvent);
var
theFunctionResult: string;
begin
Parallel.Async(
procedure
begin
// executed in background thread
theFunctionResult := GetMagicString;
end,
Parallel.TaskConfig.OnTerminated(
procedure (const task: IOmniTaskControl)
begin
// executed in main thread after the async has finished
if Assigned(AEvent) then
AEvent(theFunctionResult );
end
)
);
end;
You can then put the threaded code in the GetMagicString unit and just call the method above from your form passing in an event that will get called when it completes.
Related
I have a problem with updating some data in my DB each time, i.e each minute.
So I am using Lazarus as programming language and created linux daemon app, application contain one thread in which I have all logic which is involved in getting data from database
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Classes, SysUtils, daemonapp, Unit1, math;
....
TProcThread = class(TThread)
procedure Execute; override;
procedure UpdVars;
procedure UpdLogs;
end;
TDaemon = class(TCustomDaemon)
private
FThread: TProcThread;
procedure ThreadStopped (Sender: TObject);
public
function Start: Boolean; override;
function Stop: Boolean; override;
function Execute: Boolean; override;
function Install: Boolean; override;
function UnInstall: Boolean; override;
end;
procedure TProcThread.UpdVars;
begin
// update database
end;
procedure TProcThread.UpdLogs;
begin
// update database
end;
procedure TProcThread.Execute;
begin
repeat
while not (Query.EOF) do begin
//////
//doing stufff
//////
sleep(1000);
UpdVars;
end;
sleep(60000); // <= if I put this sleep than UpdVars don't update every second, but it updates 1sec + 60000 seconds
UpdLogs;
until Terminated;
end;
constructor TJPDaemonMapper.Create(AOwner: TComponent);
var
d: TDaemonDef;
begin
inherited Create(AOwner);
d:= DaemonDefs.Add as TDaemonDef;
d.DisplayName:= 'MyService';
d.Name:= 'myservice';
d.DaemonClassName:= 'TDaemon';
d.WinBindings.ServiceType:= stWin32;
end;
{$R *.res}
begin
Application.Title:='MyService';
RegisterDaemonClass(TDaemon);
RegisterDaemonMapper(TJPDaemonMapper);
DataModule1:= TDataModule1.Create(nil);
Application.Run;
end.
And I want to update my log table every minute, also I have one procedure UpdVars which also need to be executed every second, this one I have got by put sleep(1000) after executing this procedure in main while loop which is getting data from database. But if I put second sleep before log update procedure, I don't get needed update frequency in UpdVars procedure...
So I am stuck on this thing, how to execute procedure in specific time for each procedure it is different... can anyone help me with this problem?
Use a TTimer and a state machine. In every timer event, check if something needs to be done, and if so, advance the state machine.
A low res timer routine can also feed a scheduler. Jedi JCL has an example, but IMHO it is a bit unwieldy.
I have a Delphi (Windows) application created using Delphi 10 that has some blocking calls that I would like to convert to using threads. Unfortunately for me, these are not procedures, but functions. (And information on how to return function results from threads appears to be much more limited.) I am trying to familiarize myself with the OmniThreadLibrary, since it seems to be the most flexible and best supported threading library for Delphi, but I'm having trouble with understanding how to do this.
I have been able to get the various OmniThreadLibrary routines to work well with procedures, but when I try to set up a function, I get an error about capturing the result. When I use OmniThreadLibrary's Future example as a starting point, I can get the function to work, but I can't figure out how to connect to the event monitor, how to send messages from the task, etc. So, it seems as if I'm overlooking something no matter which way I try to solve this problem.
Currently, my program does something like this:
If myPing(IPAddress) then
begin
//Do other things hereā¦
end;
Because myPing is blocking, and I actually need it to wait until myPing returns true before processing further, the application gets sluggish during this process. I'd like to put the myPing call in a thread, which would solve the sluggishness problem, but I can't figure out how to do that in the form of a function using OmniThreadLibrary. (Unless I use a future, in which case I can't figure out how to connect to the Event Monitor.)
Edit 1: Since my original post, I have made a little progress. I was able to connect the Event Monitor to the Future by adding Parallel.TaskConfig.MonitorWith(Form1.OmniEventMonitor1) to my code, right after the function. However, I still can't figure out how to send messages to that event monitor from within the Future function.
Edit 2: I now have some sample code. My first attempt was similar to this:
function myPing(HostName: string): IOmniFuture<boolean>;
begin
Result := Parallel.Future<boolean>(function: boolean
begin
Result := False;
//Do actual ping here... Set Result := True if successful.
end
);
end;
The basic function worked, but did not allow me to send any messages to the TOmniEventMonitor. I was able to get that part working by changing the code to this:
function myPing(HostName: string): IOmniFuture<boolean>;
begin
Result := Parallel.Future<boolean>(function: boolean
begin
Result := False;
//Do actual ping here... Set Result := True if successful.
end,
Parallel.TaskConfig.MonitorWith(Form1.OmniEventMonitor1)
);
end;
Now, I can successfully monitor the OnTaskTerminated event, but I still can't send messages to the Event Monitor from the task. By changing my code once again, I can access the task itself and send messages using task.Comm.Send(), but the messages don't reach the EventMonitor:
function myPing(HostName: string): IOmniFuture<boolean>;
begin
Result := Parallel.Future<boolean>(function(const task: IOmniTask): boolean
begin
Result := False;
//Do actual ping here... Set Result := True if successful.
task.Comm.Send(0,'Test 1');
end,
Parallel.TaskConfig.MonitorWith(Form1.OmniEventMonitor1)
);
end;
Here's a simple example on how to retrieve the function result from the async call. It does not use an "OmniEventMonitor" but instead calls a function once the async call returns ("Ping" is defined in PingU.pas, but not of importance here):
unit MainFormU;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Menus;
type
TPingResultEvent = procedure (const bResult: Boolean) of object;
TOnTerminateTestForm = class(TForm)
LogMemo: TMemo;
MainMenu: TMainMenu;
PingMenu: TMenuItem;
procedure PingMenuClick(Sender: TObject);
private
procedure BackgroundPing (const sServer: String;
const OnResult: TPingResultEvent);
procedure PingResult (const bResult: Boolean);
public
{ Public declarations }
end;
var
OnTerminateTestForm: TOnTerminateTestForm;
implementation
{$R *.dfm}
uses PingU, OtlParallel, OtlTaskControl;
procedure TOnTerminateTestForm.PingMenuClick (Sender: TObject);
var
sServer : String;
begin
if (InputQuery ('Ping computer', 'Computer name:', sServer)) then
if (sServer <> '') then
begin
PingMenu.Enabled := false;
LogMemo.Lines.Add (Format ('Pinging %s',[sServer]));
BackgroundPing (sServer, PingResult);
end; { if }
end; { TOnTerminateTestForm.PingMenuClick }
procedure TOnTerminateTestForm.BackgroundPing (const sServer: String;
const OnResult: TPingResultEvent);
var
bResult : Boolean;
begin
Parallel.Async (
procedure
begin
bResult := Ping (sServer);
end,
Parallel.TaskConfig.OnTerminated(
procedure (const task: IOmniTaskControl)
begin
// executed in main thread after the async has finished
if Assigned (OnResult) then
OnResult (bResult);
end
)
);
end; { TOnTerminateTestForm.BackgroundPing }
procedure TOnTerminateTestForm.PingResult (const bResult: Boolean);
begin
PingMenu.Enabled := true;
LogMemo.Lines.Add ('Ping result = ' + BoolToStr (bResult, true));
end; { TOnTerminateTestForm.PingResult }
end.
Code source: Get a function result asynchronously in Delphi using Omni Thread Library
My database is in a VPS and I should get some query from my tables
Because of getting query from server taking long time ( depending on Internet speed ! ) , I want to use threads to get queries
Now I create a thread and get query and then send result to my forms with sending and handling messages
I want to know is it possible to create and use a thread locally ?!?
My mean is :
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;
The main part is last part ( Do next jobs ... ) , I dont want to use query result in a message handler and I want to use them in the same procedure and after thread job
Is it possible ?!
I think this is not possible with Delphi TThread class and I should use other threading techniques ...
I`m using Delphi XE6
What you describe is not the best use of a thread. The calling code is blocked until the thread is finished. That negates the use of running code in parallel at all. You could just perform the query directly instead:
procedure Requery;
var
...
begin
...
// run query
// do next jobs with query
...
end;
That being said, since you are using XE6, you can create a "local" thread by using the TThread.CreateAnonymousThread() method, specifying an anonymous procedure that "captures" the variables you want it to work with, eg:
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;
Alternatively:
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;
However, threading is more useful when you let it run in the background while you do other things and then you act when the thread has finished its work. For example:
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;
I think that using a thread this way isn't a good idea, but the answer is yes. You can do it.
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;
Yes you can do that.
The way I would do it is to add an event-handler to your form.
You'll have to link the event-handler in code, but that's not that difficult.
Create a thread like so:
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;
Now the query will run in the background and signal your event handler when it is done. Meanwhile you can do all the mousing around and user interaction you want without having to worry. Note that you cannot use FDQuery1 at all whilst the thread is using it, and you cannot have FDQuery1 wired to a DataSource whilst it's the thread is running with it.
Leave it unwired and wire it in the ThreadDone event handler as shown.
I am using Delphi 2007 and threads.
My problem (sorry, i'll try to explain better):
1) I created a file "utilities.pas" where i have the function i use more.
2) I created a new program, in this program i have one thread
3) in the execute method of the thread i call one function in my file "utilities.pas".
this function connects to an ftp using clever components (tclftp). This components logs the server responce in a dedicated event. What i would like to do is to save the log in a stringlist and then send the stringlist back to the calling thread.
This is part of the file "utilities.pas":
// I created TEventHandlers because it's the only way to assign the event runtime
// without having a class
type
TEventHandlers = class
procedure clFtp1SendCommand(Sender: TObject; const AText: string);
end;
var EvHandler: TEventHandlers;
// this is the porcedure called from the thread. i want to send the stringlist
// back to it containing the ftp log
procedure Test(VAR slMain: tStringlist);
var cFTP: TclFtp;
begin
cFTP := TclFtp.Create(nil);
cFTP.Server := 'XXX';
cFTP.UserName := 'XXX';
cFTP.Password := 'XXX';
cFTP.OnSendCommand := EvHandler.clFtp1SendCommand;
// i connect to the ftp
cFTP.Open;
FreeAndNil(cFTP);
end;
procedure TEventHandlers.clFtp1SendCommand(Sender: TObject; const AText: string);
begin
// here the component (cftp) sends me back the answer from the server.
// i am logging it
// HERE IT'S THE PROBLEM:
// I can't reach slMain from here.....
slmain.add(Atext);
end;
this is the calling thread:
procedure TCalcThread.Execute;
var slMain: tstringlist;
begin
inherited;
slmain := tstringlist.create(nil);
Test(slmain);
if slMain.count > 0 then
slMain.savetofile('c:\a.txt');
// i won't free the list box now, but in the thread terminated.
end;
this is the main program:
procedure TfMain.ThreadTerminated(Sender: TObject);
Var ExThread: TCalcThread;
begin
ExThread := (Sender as TCalcThread);
if ExThread.slMain.Count > 0 then
ExThread.slMain.SaveToFile('LOG\Errori.log');
freeandnil(slMain);
end;
Please can anybody help me in solving this? I really don't know what to do.
I hope now it more clear.
p.s. thanks for all the answer..
Another approach would be to have your thread object have its own instance of the stringlist and its own cFTP. If you need to have one "master thread" that everything writes to (perhaps for a summary of what each thread accomplished), use this class:
TThreadStringList by Tilo Eckert
http://www.swissdelphicenter.ch/torry/showcode.php?id=2167
I think one (BAD) approach would be to create a pool of components in the main thread or at design time, and assign one to each thread. i.e. 5 instances of cFTP, 5 stringlists, 5 threads.
Update: Martin James points out why this is a terrible idea, and I agree. So don't do this. Post stays as a deterrent.
Intercept the event within the thread class, and fire an own typed event from within that handler. Synchronize this call! And try to prevent the global variable. All this as follows:
type
TFtpSendCommandEvent = procedure(Mail: TStrings; const AText: String) of object;
TMyThread = class(TThread)
private
FclFtp: TclFtp;
FslMail: TStrings;
FOnFtpSendCommand: TFtpSendCommandEvent;
FText: String;
procedure clFtpSendCommand(Sender: TObject; const AText: String);
procedure DoFtpSendCommand;
protected
procedure Execute; override;
public
// You could add this property as parameter to the constructor to prevent the
// need to assign it separately
property OnFtpSendCommand: TFtpSendCommandEvent read FOnFtpSendCommand
write FOnFtpSendCommand;
end;
// If you dont want to make this a property or private field of the thread class:
var
EvHandler: TFtpSendCommandEvent;
{ TMyThread }
procedure TMyThread.clFtpSendCommand(Sender: TObject; const AText: string);
begin
// Store the AText parameter temporarily in a private field: Synchronize only
// takes a parameterless method
FText := AText;
Synchronize(DoFtpSendCommand);
end;
procedure TMyThread.DoFtpSendCommand;
begin
if Assigned(FOnFtpSendCommand) then
FOnFtpSendCommand(FslMail, FText);
// Or, if you really like to use that global variable:
if Assigned(EvHandler) then
EvHandler(FslMail, FText);
end;
procedure TMyThread.Execute;
begin
...
FclFtp := TclFtp.Create(nil);
FslMail := TStringList.Create(nil);
try
FclFtp.Server := 'XXX';
FclFtp.UserName := 'XXX';
FclFtp.Password := 'XXX';
FclFtp.OnSendCommand := clFtpSendCommand;
FclFtp.Open;
finally
FreeAndNil(FclFtp);
FreeAndNil(FslMail);
end;
...
end;
there is a very complex application which i try to build.
There is a DLL library which i create. I put a form in it and i put a Thread in it.
in DLL i have a function:
procedure ShowForm; stdcall;
var
Form1 : TFormSNVFL7;
begin
Form1 := TFormSNVFL7.Create(nil);
Form1.Show;
end;
i create a form and show it. there isn't problem in here.
I add a thread to this dll.
i put a timer on the form. after a couple of seconds i create a thread and run it. everything is going normal but when i try to change anything of the form, nothing happen.
in synchronize function i try to change a label on it but nothing happen.
Here is the files:
DLL pas:
library uploader;
uses
SysUtils,
Classes,
Forms,
UploaderForm in 'UploaderForm.pas' {FormUploader},
ThreadUpload in 'ThreadUpload.pas';
{$R *.res}
procedure ShowForm; stdcall;
var
upForm: TFormUploader;
begin
upForm := TFormUploader.Create(nil);
upForm.Show;
end;
exports
ShowForm;
begin
end.
Form pas:
unit UploaderForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, acPNG, ExtCtrls, JvExExtCtrls, JvImage, JvExControls, JvLabel,
JvAnimatedImage, JvGIFCtrl, ComCtrls, JvExComCtrls, JvProgressBar, StdCtrls,
FileCtrl, JvDriveCtrls;
type
TFormUploader = class(TForm)
imgRunning: TJvImage;
imgReady: TJvImage;
imgUpdate: TJvImage;
JvLabel1: TJvLabel;
JvLabel2: TJvLabel;
imgConnect: TJvImage;
imgUpload: TJvImage;
imgCheck: TJvImage;
JvLabel3: TJvLabel;
JvLabel4: TJvLabel;
JvLabel5: TJvLabel;
JvLabel6: TJvLabel;
imgRun: TJvImage;
imgOK: TJvImage;
imgDone: TJvImage;
JvProgressBar1: TJvProgressBar;
JvLabel7: TJvLabel;
fileList: TJvFileListBox;
Timer1: TTimer;
procedure FormCreate(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
FormUploader: TFormUploader;
implementation
{$R *.dfm}
Uses ThreadUpload;
procedure TFormUploader.FormCreate(Sender: TObject);
begin
imgUpdate.Picture := imgReady.Picture;
imgConnect.Picture := imgReady.Picture;
imgUpload.Picture := imgReady.Picture;
imgCheck.Picture := imgReady.Picture;
imgRun.Picture := imgReady.Picture;
imgOK.Picture := imgReady.Picture;
fileList.Directory := ExtractFilePath(Application.ExeName) + 'csvexport/';
end;
procedure TFormUploader.Timer1Timer(Sender: TObject);
var
UpThread: TThread;
begin
Timer1.Enabled := False;
UpThread := UploadThread.Create(true);
UpThread.Create;
UpThread.Resume;
end;
end.
Thread pas:
unit ThreadUpload;
interface
uses
Classes, UploaderForm;
type
UploadThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
implementation
{ UploadThread }
procedure UploadThread.Execute;
begin
With FormUploader do
begin
imgUpdate.Picture := imgRunning.Picture;
end;
end;
end.
i cannot solve this problem.
TThread.Synchronize() does not work in a DLL by default, as the sync queue that Synchronize() posts to is local to the executable that is calling it. In other words, when Synchronize() is called by the application, it posts to a queue that is local to the exe file. When Synchronize() is called by a DLL, it posts to a queue that is local to the dll file. When the application pumps its sync queue during idle times, it will not pump the DLL's queue automatically. You have to export a function from your DLL that your application can then call when needed, such as in the TApplication.OnIdle event or in a timer. That exported function can then call the RTL's CheckSynchronize() function to pump the DLL's sync queue.
Simple
You are changing a property from the FormUploader var from the unit UploaderForm in the UpThread
But in the unit DLL.pas you are creating other object from TFormUploader
Try to do this in the procedure that show the form:
procedure ShowForm; stdcall;
begin
FormUploader := TFormUploader.Create(nil);
FormUploader.Show;
end;
Do this, and the problem are solved
Your problem results from using VCL and threading. You should never call VCL related code from a thread without using synchronization mechanisms.
Normally you create a VCL application by using TApplication and TApplication.Run() to create a main loop of your program. The main loop handles windows messages and other stuff, but also calls CheckSynchronize() whereas CheckSynchronize() looks up whether there is call queued that should be synchronized (that is a call that is added to the queue by using TThread.Synchronize()). So when you create a thread i runs concurrently to the main loop and that's where your problem begins.
You should either move the picture assignment code to a separate method in TFormUploader and call that method by using TThread.Synchronize() or use other synchronization mechanisms like event objects (TEvent / CreateEvent()).
I had a similar issue trying to update a TToolButton icon in the main EXE, from a callback function invoked by the DLL. The DLL invokes the callback function in response to a broadcast to channel message sent via a DataSnap implementation, I think in a child thread.
Accessing the TToolButton directly from the EXE callback function results in flickering the TToolBar and disappearing the icons.
I created a TThread object and the interaction with the TToolButton is managed using the TThread.Synchonize() function into the main thread: this solved the problem for me.
interface
type
TCallBackThread=class(TThread)
private
procedure DoInSync;
public
procedure Execute; override;
end;
var
CallBackThread: TCallBackThread;
implementation
procedure TCallBackThread.DoInSync;
begin
// Jobs to be done in main thread
end;
procedure TCallBackThread.Execute;
begin
inherited;
Synchronize(DoInSync);
end;
The callback function into the EXE is:
procedure ConnectWf_Callback(s: PAnsiChar); stdcall;
begin
if not Assigned(CallBackThread) then begin
CallBackThread := TCallBackThread.Create(true);
CallBackThread.Resume;
end else begin
CallBackThread.Execute;
end;
end;