Jump to content
pyscripter

Asynchronous Programming Library

Recommended Posts

Is anybody using the Asynchronous Programming Library

 

AFAICT, it works like TThread.Synchronize/Queue/ForceQueue, but it gives you greater control on the tasks executed asynchronously.   (Cancel, Wait etc.)

TComponent.BeginInvoke(procedure begin end, nil).Invoke just uses TThread.Queue.

TWinControl.BeginInvoke(procedure begin end, nil).Invoke processes the procedure in the event loop of the WinControl.

 

Are there any examples of usage?

Edited by pyscripter

Share this post


Link to post
1 hour ago, pyscripter said:

Is anybody using the Asynchronous Programming Library

 

AFAICT, it works like TThread.Synchronize/Queue/ForceQueue, but it gives you greater control on the tasks executed asynchronously.   (Cancel, Wait etc.)

TComponent.BeginInvoke(procedure begin end, nil).Invoke just uses TThread.Queue.

TWinControl.BeginInvoke(procedure begin end, nil).Invoke processes the procedure in the event loop of the WinControl.

 

Are there any examples of usage?

Nope. Frankly, it is abomination. My primary objection is that it slaps control flow into TComponent where it does not belong. TComponent is not thread safe class and asynchronous operations and multithreading imposed on that level open recipe for disaster.

It does not give you more control, it takes it away from you and it is overly complicated. Maybe there are some small use cases, but I still haven't found any. 

At the same time we don't have cancellation token implemented in PPL which desperately needs it. Good implementation of cancelation tokens could then be used in other code even asynchronous one that does not run in threads.
 

I have no idea what is original intent behind that library, so maybe I am missing something obvious. 

  • Like 9

Share this post


Link to post

A very odd design, I can't imagine why they thought this was a good design.  

 

I you just want to run some tasks in the background, I leveraged OmniThread Library with my own wrapper that allows cancellation (via VSoft.CancellationToken) and returning values. I use in in the ui for the package manager I am working on, changing tabs invokes a background request to fetch package lists, however if you click on another tab before the first request is finished, it will cancel the first request and start a new one. Very simple to use. 

  • Like 2

Share this post


Link to post

Thinking about this some more overnight, I suspect this was designed by the same persion who designed the TTitlebar abomination (everything is arse backwards) - why on earth is all this functionality embedded in a TComponent - TComponent just getting more and more heavyweight with code that can't be removed/replaced with a better design later (like TForm now). 

 

Does embarcadero have a chief architect these days - someone to guide the overal design of the rtl and frameworks? Doesn't seem like it. I miss the days when we have people like Allen Bauer - every major feature was carefully considered with regards to future impact etc.  

  • Like 2

Share this post


Link to post
1 hour ago, Vincent Parrett said:

Does embarcadero have a chief architect these days - someone to guide the overal design of the rtl and frameworks? Doesn't seem like it. I miss the days when we have people like Allen Bauer - every major feature was carefully considered with regards to future impact etc.  

This does appear to be the work of Allen, same as the Delphi Parallel Library.  He blogged about it back in 2008.

 

 

Share this post


Link to post

OK, it may still be true that for today no one else actually knows how to use it properly.

  • Like 1

Share this post


Link to post

@Vincent Parrett@Dalija PrasnikarI was also quick to dismiss it and I disliked the fact that the functionality is under TComponent.   However, after a bit of digging it appears that  it does have some nice touches.  For example TComponent, uses Thread.Queue to run the tasks and WinControl uses the message loop.  But  a component descendent can overwrite just one method AsyncSchedule to use the Parallel Libary or OmniThread to run the tasks, e.g..

// Disclaimer: Have not tested!

type
  TPPLComp = class(TComponent)
  protected
    procedure AsyncSchedule(const ASyncResult: TBaseAsyncResult); override;
  end;

  procedure TPPLComp.AsyncSchedule(const ASyncResult: TBaseAsyncResult);
  var
    LState: TComponentState;
  begin
    LState := FComponentState; // Snag a local copy
    if csDestroying in LState then
      raise EInvalidOperation.CreateRes(@sBeginInvokeDestroying);
    TTask.Create(ASyncResult.DoAsyncDispatch).Start;
  end;

But I confess I do not see the benefits in the above compared to using TTask.Create directly.  ITask and IAsyncResult look quite similar. 

1 hour ago, Uwe Raabe said:

OK, it may still be true that for today no one else actually knows how to use it properly.

I have not tested but it does not appear to be that hard.

var Comp := TComponent.Create(nil)
var AsyncResult := Comp.BeginInvoke(procedure begin end, nil);

is equivalent to 

TThread.Queue(nil, procedure begin end);

But with a former method you can do a bunch of stuff with AsyncResult (Wait, Cancel, etc,).   Also BeginInvoke takes as an argument an Object (Context) that can be useful in some scenarios.  And BeginInvoke works with functions and allows you to get a Result back.  (like a Future).

 

It appears that the only reason BeginInvoke is a member of TComponent and not even a class function is to offer the ability to customize the dispatch in TComponent descendents:

procedure TComponent.TComponentAsyncResult.Schedule;
begin
  FComponent.AsyncSchedule(Self);
  FComponent := nil;
end;

 

Edited by pyscripter

Share this post


Link to post
34 minutes ago, Uwe Raabe said:

Ahem, AFAIK it was in fact Allen Bauer who laid out the architecture of the APL - if not actually wrote the whole stuff himself: 

:classic_blush: One significant difference is he was suggesting a helper class for this rather than saddling every tcomponent descendant with it. 

 

  • Like 1

Share this post


Link to post
7 hours ago, Uwe Raabe said:

Ahem, AFAIK it was in fact Allen Bauer who laid out the architecture of the APL - if not actually wrote the whole stuff himself: 

A Sink Programming

More A Sink Kronos programming

Value Capture vs. Variable Capture

Thanks! 

 

Look at the dates... he wrote about it in 2008 and the thing was included in 2015 in XE8. Why I have the feeling it was added just to add some check mark on number of new features, without thinking about the impact. At that point having proper cancellation token would be more useful feature. How is this useful in combination with PPL I still cannot figure out.

 

Proof of concept is one thing, imposing it on TComponent is another. If it would be in domain of helper methods, then it would probably be more flexible (adjusting the behavior through inheritance is very rigid) Of course, I understand why it was implemented directly... because we can only have single helper in scope :classic_sad:

 

Maybe, it is handy for some quick RAD kind of development, where you need to bang up some example code fast and you need to make the application responsive. 

 

One place I know this is used is in System.Net. But, have you ever tried debugging such asynchronous HTTP requests. It is mission impossible, you have to jump through the hoops not knowing where you can safely put your breakpoint to land on some code you need to inspect because it is buried deep. Not to mention that handling request and response code is broken down scattered around in order to satisfy BeginInvoke/EndInvoke pattern. 

 

And if you take the look at the THTTPClient declaration, with zillion various Begin... methods, it becomes more obvious that implementing asynchronicity on this level is not the best idea. It brings needles complexity. Asynchronous frameworks should be implemented as wrappers around code and tasks (like PPL), not be implemented on the same level (from within the class that performs some operation). This simply does not scale well.

  • Like 6

Share this post


Link to post
4 minutes ago, Dalija Prasnikar said:

But, have you ever tried debugging such asynchronous HTTP requests. It is mission impossible

I can attest to this, currently rewriting my http client library using the WinHttp C api, as the com api doesn't work in the delphi ide (due to coinit issues) - I'm using it's async mode (not embarcadero's) which uses a callback.. the delphi debugger and it do not play well together! 

 

I really can't understand why more focus hasn't been put on the PPL, and seriously cancellation tokens are a must have.

 

Embarcadero don't seem to have realised that the world has moved on from everything being a component you drop on a form. If I can't see it at runtime on my screen then it doesn't need to be a component/control, I'm quite capable of instantiating an instance at runtime, and I don't need all the baggage that TComponent brings along.   

  • Like 4

Share this post


Link to post
15 minutes ago, Vincent Parrett said:

Embarcadero don't seem to have realised that the world has moved on from everything being a component you drop on a form. If I can't see it at runtime on my screen then it doesn't need to be a component/control, I'm quite capable of instantiating an instance at runtime, and I don't need all the baggage that TComponent brings along.   

Low code is still a thing, or at least that is what "marketing" is selling to us as greatest feature ever. 

 

It may be good for non programmers that want to put few things together, but it is really bad for any even remotely complex application. The fastest way to sink an application is to drown it in low code.

 

I had my share of bad design choices some ten years ago... following similar route - slap everything on TComponent and drag and drop design... I am still maintaining that crap and I cannot easily untangle the mess.... I wish someone hit me on the head back then, it would spare me a lot of trouble.

Share this post


Link to post
5 hours ago, Dalija Prasnikar said:

How is this useful in combination with PPL I still cannot figure out.

IAsyncResult is a higher level of abstraction than PPL. You want the async backend to be TThread.Queue, Anonymous Threads, PPL, OmniThread, processing in a message loop, all you have to do is overwrite one method. 

 

5 hours ago, Dalija Prasnikar said:

But, have you ever tried debugging such asynchronous HTTP requests.

I would argue that debugging any asynchronous application is kind of hard to put it mildly.

Edited by pyscripter

Share this post


Link to post

It is VERY hard. Especially when you can't persuade the debugger to break ONLY in the thread(s) that you want to debug.

Share this post


Link to post
1 minute ago, pyscripter said:

IAsyncResult is a higher level of abstraction than PPL. You want the async backend to be TThread.Queue, Anonymous Threads, PPL, OmniThread, processing in a message loop, al you have to do is overwrite one method. 

IAsyncResult as abstract interface is not a problem. It is not a problem that you can choose the async backend. It is a problem because that backend support is directly backed inside the class that needs to do some work. It is a problem because that is all done via inheritance on classes that should not care about all that. It is abstraction imposed on a wrong level. 

 

1 minute ago, pyscripter said:

I would argue that debugging any asynchronous application is kind of hard to put it mildly.

Not necessarily. It is difference between debugging and inspecting your code, vs landing on some irrelevant framework pieces to inspect what is happening and where you have particular problem.

 

Try debugging this, where it is questionable will you even land on RequestCompleted handles in case of exception in the request

procedure TMainForm.Button1Click(Sender: TObject);
begin
  HttpClient.Asynchronous := True;
  HttpClient.Get('http://httpbin.org/get');
end;

procedure TMainForm.HTTPRequestRequestCompleted(const Sender: TObject; const AResponse: IHTTPResponse);
begin
  TThread.Synchronize(nil,
    procedure
    begin
      Memo.Lines.Add(AResponse.ContentAsString);
    end);
end;

versus debugging this

 

procedure TMainForm.Button1Click(Sender: TObject);
begin
  TTask.Run(
    procedure
    var
      Client: THTTPClient;
      Response: IHTTPResponse;
    begin
      Client := THTTPClient.Create;
      try
        Response := Client.Get('http://httpbin.org/get');
        TThread.Synchronize(nil,
          procedure
          begin
            Memo.Lines.Add(Response.ContentAsString);
          end);
      finally
        Client.Free;
      end;
    end);
end;

 

Yes, the second example is longer, but it is all in code and does not require any design time wiring. 

 

Another problem here is that because THttpClient implements support for IAsyncResult, it is rather complex class. So stepping into such code regardless of how you ended up there will be hard to follow. Without IAsyncResult that class could be much simpler not just to debug and understand, but also easier to maintain.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×