- Could it be useful to others?
- Could I use it in other Apps?
- Could it be useful to have it as TWhatever<T>?
So if one of 1-3 is true - I have a new class/function in one FDK-Unit or perhaps a new Unit at all.
The next thing is: Where would I like to execute this stuff - Main-thread or Background? If the answer is Background the next question is how! With existing threads or a new one? Should the User just place it into a TTask.Run?
Threading is a big part of the FDK and there are 14 Units at the moment dealing with threading. Some of them using a basic thread implementation from another unit, some implementing their own Threads because of special needs.
So for this blog post lets assume background operation, but every background execution has some point of the interface into the Main-Thread. And here comes the question:
Where to do the synchronization?
Let's have a little example:
Procedure Button1Click(Sender : TObject)
var
LMemoText : String;
begin
Button1.Enabled := false;
TAsync
{} .Await( Procedure
begin
LMemoText := THTTPRead.URL(SomeURL);
end )
{} .Execute( Procedure
begin
Memo1.Lines.Text := LMemoText;
Button1.Enabled := true;
end );
end;
So the Await procedure is running in a thread, that's why I set a string and not directly the memo. The Execute procedure is called in MainThread to handle UI-Stuff. So the synchronization is handled internally. It could be different... But this is the common usage of this TAsync. I Could write it so:
Procedure Button1Click(Sender : TObject)
begin
Button1.Enabled := false;
Button1.Enabled := false;
TAsync
{} .Await( Procedure
var
LMemoText : String;
begin
LMemoText := THTTPRead.URL(SomeURL);
TThread.Queue(NIL,Procedure
begin
Memo1.Lines.Text := LMemoText;
end);
end )
{} .Execute( Procedure
begin
Button1.Enabled := true;
end );
end;
But in this example the synchronization is done twice...
Back to the question: Is the internal synchronization of the Execute part a good idea? In the example I think yes.
Sometimes internal synchronization could be a bad idea especially if TThread.Synchronize is used and not Queue but in the next example even Queue did not help, and here is the reason:
Normally I would do any HTTP related things with a callback, but for this example I like to show a Modal-Call of an Async function, just for Demonstration.
Normally I would do any HTTP related things with a callback, but for this example I like to show a Modal-Call of an Async function, just for Demonstration.
Procedure THTTPRead.URL(Const AURL : String) : String;
var
LEvent : TEvent;
LResult : String;
begin
LEvent := TEvent.Create(NIL,true,false,'');
try
THTTPTAsync.URL(AURL, Procedure (Const AResult : String)
begin
LResult := AResult;
LEvent.SetEvent;
end);
LEvent.Wait;
finally
LEvent.Free;
end;
Result := LResult;
end;
Don't do this ;-) but if, then hope that the Result procedure is not called in a TThread.Queue, because this would be a dead-lock. If you have a Threaded or Async Class/Function use it always as it was designed for.
On the other hand, I hate to put in a TThread.Queue in every Call-Back that's why I often design an optional parameter.
THTTPTAsync.URL(AURL, Procedure (Const AResult : String)
begin
_Result := AResult;
LEvent.SetEvent;
end,false); // false = no sync
For better code reading perhaps rename the call like
THTTPTAsync.URL_NoSync(AURL, Procedure (Const AResult : String)
begin
// Whatever
end);
or
THTTPTAsync.URL(AURL, Procedure (Const AResult : String)
begin
// Whatever
end,TSync.No); // TSync = (Yes,No);
Honestly, I mostly just use a boolean, but I'll put it on my list for refactoring.
So with this approach, I have the best of both...
No comments:
Post a Comment