Delphi Threads & UserInfo

Hi All

I’m still new to Dataabstract and just have a few basic questions.

  1. My Custom DA server runs well on an Amazon EC2 server. However, opening of Data tables and Apply Updates takes from 1 - 5 seconds. If I call MemTable.Open in the main thread, it blocks it and I cannot display an ActivityIndicator. Same goes for ApplyUpdates. I have been experimenting by calling MemTable.Open and MemTable.ApplyUpdates inside a TThread. It appears to be working and the threads just stays alive purely to execute Open and ApplyUpdates. Is this an acceptable practice? Do I need to do anything else inside the thread? Should I call disable controls on the dataset before I send it to the thread?

  2. In my Login function on the server, I set som data for the UserInfo. I have also followed the steps by steps how to create my own UserInfo struct. I have done so, but how do I access such information on the client? Both from the standard UserInfo and my own custom UserInfo struct. Please give me som basic examples.

By the way, I’m using the latest DA release and Delphi 10.2

Any help would be highly appreciated.

Well, after testing ab it more, it do seem that calling the DataTable.Open in a thread messes up something further down the road. Eventually, the app freezes and crash.

How are all you other guys doing this when there is a delay for fetching an writing data? How do you let the user know that something is indeed happening? I tried without doing anything, but most of the user complained that “your app does not work!” I click on this button and nothing happens. Well, 5 seconds later data arrives, but my users are not satisfied with this

Review possibility to receive not all hundred thousand records, but filtered records by some criteria.
if you have a big number of fields in table, you can use ReducedDelta feature. It will allow to send to server only changed records during ApplyUpdate.

You can open/update table asynchronously if you need this.
see BeginFill/EndFill and BeginApplyUpdates/EndApplyUpdates methods of TDARemoteDataAdapter.


if descendant of UserInfo is put into Login method on server-side, you can get it on client-side via casting like

if aUserInfo is TMyUserInfo then myUserInfo := TMyUserInfo(aUserInfo)

Thanks for your answer.

My apologies for not catching what you say here. Where exactly in the client do I find UserInfo? My login procedure was generated by the wizard, but I cannot see any reference to the UserInfo on the client. After a successful login, where can I access the UserInfo?

As for the loading of data in the thread, I have spent 6 hours looking for examples or any info on how to use BeginFill / EndFill and BeginApplyUpdates / EndApplyUpdates, but I cannot find any good info or examples on how to do this. Again, I have only been with DA for a couple of months.

Now in the beginning until I get a bigger picture of DA, I think I will settle by loading data in a thread. Again, the wizard generated a bunch of MemTable components and I would like to open them and load data in a thread. I have simply tried to call the MemTable.Open in the thread, but that creates a whole lot of problems. Then I tried to use the Syncronize(OpenTable) in the thread, but that locks up the main thread. I think I read somewhere that only the client channel is actually thread safe. Should I create all the components like DAMemTable, Message, RemoteService, DataStreamer, RemoteDataAdapter and ReconsileProvider inside the thread before I call the Open() function? So if I do just that, how to get the loaded data in the created DAMemTable (in the thread) assigned to the one in the Main Thread where all my data access components sits?

Sorry again for not understanding it all. I’m trying as hard as I can :slight_smile:

Thanks again for any information

DAD Wizard generates code for LoginEx method. You need to use Login method instead.
Replace

function TClientDataModule.LogOn(user, password: String): Boolean;
...
  fIsLoggedOn := (TBaseLoginService_Proxy.Create('LoginService', Message, ClientChannel) as IBaseLoginService).LoginEx({$IFDEF CODEGEN4_LEGACYSTRINGS}UTF8Encode{$ENDIF}(CreateConnectionString(user,password)));

with

function TClientDataModule.LogOn(user, password: String): Boolean;
...
  fIsLoggedOn := (TSimpleLoginService_Proxy.Create('LoginService', Message, ClientChannel) as ISimpleLoginService).Login(user,password,myuserinfo);

Pls review RO\Phone Photo Server sample. it demonstrates usage of such Begin/End* methods.

in brief, you need to call

RemoteDataAdapter.BeginFill(mycallback, nil, [table1, table2]);

in mycallback, which will be fired after BeginFill was finished, you need to have something like

procedure TClientForm.mycallback(const aRequest: IROAsyncRequest);
begin
  if (not aRequest.Cancelled) then begin
    RemoteDataAdapter.EndFill(aRequest);
    ShowMessage('Tables were loaded');
  end
  else 
    ShowMessage('Request was cancelled!');
end;

it depends on how you want to consume data from MemDataTable. if you want to consume it with GUI controls, like TDBGrid, etc, better to have tables on datamodule, i.e. maintain them in main thread. if data should be consumed internally, it can be processed in background threads.

some tricks:

  • you can call DisableControls/Open/EnableControls methods of table. in some cases, it may improve situation.
  • you can open several tables in one request with RemoteDataAdapter.Fill, like:
RemoteDataAdapter.Fill([table1, table2]);

EvgenK

Now thats an answer :smile:

This will get me going with my login service and async fetch

Thanks again

Eivind

Quick question EvgenK

I got the Async loading of data to work (sort of). My first request to BeginFill and EndFill works good. However, on my next batch of datasets to load I get an error when calling EndFill. The error says: “Cannot Initialize Streamer that is already in use”

Surely I’m missing something basic here. I do not attempt to load more data until the first batch is finished and the callback have already been called as described above. Correct data is received from the first batch.

Thanks again for any pointers

More Info… After some desperate testing, it seams that only my “simple” DAMemDataTables can be opened this way. Meaning, no lookup fields, etc. If I have defined a lookup field, the above error message persists. Just for the fun, I also tried RemoteDataAdapter.Fill and it fails in the same manner as the EndFill

can you create a simple testcase for such failure, pls?
you can attach it here or drop email to support.

temporary workaround for “Cannot Initialize Streamer that is already in use” can be - add events OnInitialized/OnFinalized to DataStreamer like

//fStreamerState: Boolean;
procedure TClientDataModule.DataStreamerFinalized(Sender: TObject);
begin
  fStreamerState:=False;
end;

procedure TClientDataModule.DataStreamerInitialized(Sender: TObject);
begin
  fStreamerState:=True;
end;

and add check to it before calling EndFill like

while  fStreamerState do DataStreamer.Finalize;

Hi EvgenyK

I can confirm that the “Cannot Initialize Streamer” error only comes on tables that has lookup fields defined. Any other table that does not have a lookup fields loads perfectly with Fill() and BeginFill(). Once a table with lookup fields is filled, I guess it tries to automatically open other tables that is used in the lookup fields. If I simply try to Call DataTable.Open(), all other lookup datasets are open automatically. This seams not to be the case with Fill() and BeginFill(). It appears the lookup datasets tries to load before the master dataset has completed using the streamer.

Anyhow, I then tried to add all those lookup datasets in the same Fill() or BeginFill() array of datasets, and this solved the issue.

How does this sounds?

thanks for info, I’ll try to reproduce this locally


Edit: I can reproduce this behavior.

workaround:

  • Open lookup tables in one call or open them before such tables.
  • ofc, you can have personal RDA&DataStreamer for lookup tables, but 1st workaround is better.