Thread safety and IBXComponents variable

edited 4:45AM in FastReport 4.0
Hello

Is it possible to write thread safe reports using frxIBXComponents ? There is (Delphi 5 Source) a global variable:


var
IBXComponents: TfrxIBXComponents;

which seems to be a problem in multithreaded enviroment.

I want to prepare and create two or more reports in separate threads in the same time.

b

Comments

  • gpigpi
    edited 4:45AM
    See a demo based on TfrxADOComponents and D7 in attach
  • edited 4:45AM
    gpi wrote: »
    See a demo based on TfrxADOComponents and D7 in attach

    I've asked about IBX : ) not ADO. I don't know if there is same solution like global IBXComponents variable ? maybe ADOComponents variable ? Can You check this ?

    b

  • edited 4:45AM
    What errors did you get while trying to compile and run examples given by Gpi after you had changed ADO into IBX to test this idea for your specific case?

    Mick
  • edited 4:45AM
    Mick.pl wrote: »
    What errors did you get while trying to compile and run examples given by Gpi after you had changed ADO into IBX to test this idea for your specific case?

    Mick
    I didn't try (I will be able to to this in Monday). However I've post this question because we have problem whit multithreading with frxIBXComponents. There are no compile errors - there are runtime errors. What kind ? - very different:

    Database not assigned
    invalid request handle
    Error reading data from the connection
    invalid transaction handle (expecting explicit transaction start)
    unknown ISC error 0
    Transaction not assigned

    and many others. Those problem occurs "sometimes" when two threads prepare two reports in the same time.

    Is it so hard to believe that global variable maybe a problem in simultaneously preparing reports from threads ?

    But OK - i will try to prepare a test case.

    P.S. in GPi example there are lot of other errors : ) but most of all are result of access to components from thread without synchronization ...

    {...}

    procedure TPrepareThread.Done;
    begin
    FLB.Caption := 'Done';
    FBtn.Enabled := True;
    end;

    procedure TPrepareThread.Ended;
    begin
    FLB.Caption := 'Not Started';
    FBtn.Enabled := False;
    end;

    {...}

    It's bad idea - but I know that is only example of other issue : )

    b
  • edited 4:45AM
    There is one more thing, i didn't mention. I our case, each thread has its own datamodule. This datamodule has it's own IBDB : TIBDatabase component. This datamodule has also frxReport component and frxIBXComponent. The property DefaultDatabase of frxIBXComponent ponits to IBDB. The datamodule is not a global variable. It's private field of thread, it is created in thread constructor. In Gpi example in each report there was a ADODatabase component - maybe that is the most important difference ?

    P.S. Sorry for my comment about synchronization : ) I didn't notice Synchronization method before : ) However i get a lot of error when running this example.


    b
  • edited 4:45AM
    So after changes in your example:


    - create new datamodule which contains:
    - frxreport
    - frxADOComponents
    - ADOConnection

    - frxADOComponents.DefaultDatabase point to ADOConnection, ADOConnection points to demo.mdb

    - in the 1.fr3 report i've removed the ADODatabase, and change query database to ADOConnection

    - each thread load and prepare the same report - based on 1.fr3 (saved as 2.fr3 and 3.fr3)

    - thread instead of createing frxreport - creates datamodule

    And now, when I click button 1, button 2, button 3 It works like before, but when I put button 4 with code:

    FRepThread1 := TPrepareThread.Create(AppPath + '1.fr3', Button4, Label1);
    FRepThread2 := TPrepareThread.Create(AppPath + '2.fr3', Button5, Label2);
    FRepThread3 := TPrepareThread.Create(AppPath + '3.fr3', Button6, Label3);

    there are many different errors like "OLE Error", "Operation not allowed when ..."

    b
    b
  • edited 4:45AM
    And final test : )

    After removing frxADOComponent from Datamodule, and putting back ADOConnection into 1.fr3, all works fine (even fire all three report in the same time in timer).

    So - my final question is:

    It is possible to prepare and create report's in thread using frxXXXComponents (frxIBXComponents, frxADOComponents) ?

    b
  • gpigpi
    edited 4:45AM
    wrote:
    P.S. in GPi example there are lot of other errors : )
    Try to modify my code:
    procedure TPrepareThread.OnClose(Sender: TObject);
    begin
      if FReport <> nil then
      begin
        FReport.OnClosePreview := nil;  //add
        if FReport.PreviewForm <> nil then FReport.PreviewForm.Close; //add
        FReport.Free;
        FReport := nil;
      end;
      Synchronize(Ended);
      Terminate;
    end;
    
  • edited 4:45AM
    gpi wrote: »
    gpi wrote: »
    P.S. in GPi example there are lot of other errors : )
    Try to modify my code:
    procedure TPrepareThread.OnClose(Sender: TObject);
    begin
      if FReport <> nil then
      begin
        FReport.OnClosePreview := nil;  //add
        if FReport.PreviewForm <> nil then FReport.PreviewForm.Close; //add
        FReport.Free;
        FReport := nil;
      end;
      Synchronize(Ended);
      Terminate;
    end;
    

    I gues that this is not the solution for primary problem ; )

    b
  • gpigpi
    edited 4:45AM
    First, attach small test application based on employee.gdb and standart Delphi components to show your primary problem. At this moment I see only words
  • edited 4:45AM
    gpi wrote: »
    First, attach small test application based on employee.gdb and standart Delphi components to show your primary problem. At this moment I see only words
    I think the problem exists also for ADO. All You have to do is to use frxADOComponents and set it DefaultDatabase to ADOConnection ...

    I will prepare a test case later.

    But generally I gues that Your reply will be: frxXXXComponents are not intended to be used in threads in such way : )

    b
  • edited 4:45AM
    Or in other words - DefaultDatabase option is not designed to use in threads.
  • gpigpi
    edited 4:45AM
    wrote:
    After removing frxADOComponent from Datamodule, and putting back ADOConnection into 1.fr3, all works fine (even fire all three report in the same time in timer).
    Did you use separate frxADOComponent for each thread?
  • edited 4:45AM
    gpi wrote: »
    gpi wrote: »
    After removing frxADOComponent from Datamodule, and putting back ADOConnection into 1.fr3, all works fine (even fire all three report in the same time in timer).
    Did you use separate frxADOComponent for each thread?

    Yes I do, but - is there such variable:

    var
    ADOComponents: TfrxADOComponents;

    ? I don't have a source, but in frxIBXComponent there is such variable:

    var
    IBXComponents: TfrxIBXComponents;


    and imho that is the problem.

    I've attached Your reworked example.

    b
    Test.zip 265.2K
  • edited 4:45AM
    Do you mean you don't have frxADOComponents.pas? It comes alltogether with the source of FR in ADO folder, a twin one to IBX folder. And I can see

    var
    ADOComponents: TfrxADOComponents;

    over there.

    By the way - how did it happen that you have frxIBXComponent.pas and do not have frxADOComponents.pas ?

    Mick
  • edited 4:45AM
    Mick.pl wrote: »
    Do you mean you don't have frxADOComponents.pas? It comes alltogether with the source of FR in ADO folder, a twin one to IBX folder. And I can see

    var
    ADOComponents: TfrxADOComponents;

    over there.

    By the way - how did it happen that you have frxIBXComponent.pas and do not have frxADOComponents.pas ?

    Mick

    You right, there is frxADOComponents. When i clicked on frxADOComponents Delphi didn't bring me into it - I don't know why.

    So if this is global variable, declared at unit level - it can be a problem, am i right ?

    b
  • gpigpi
    edited 4:45AM
    wrote:
    So if this is global variable, declared at unit level - it can be a problem, am i right ?
    Yes. You may use separate TfrxIBXDatabase in report templates or TfrxDBDataset in datamodule (with TfrxReport.EngineOptions.UseGlobalDataSetList = False)
  • edited 4:45AM
    gpi wrote: »
    gpi wrote: »
    So if this is global variable, declared at unit level - it can be a problem, am i right ?
    Yes.

    Finally ! : )

    I don't want to use TfrxIBXDatabase in each report, because of many reasons:

    - user doesn't have to know nothing about database location
    - user doesn't have to know about user / pass
    - it is easier for user to have one component (tfrxibquery) than two components (tfrxibxdatabase + tfrxibquery)
    - changing database location, can be transparent for reports
    ... etc. etc. etc.

    There is on more reason. I've many users, with many reports, without TfrxIBXDatabase component : )

    Solution with TfrxDBDataset, uhm it not sound good for me.

    Tommorow we'll try my own solution - we will use frxIBXComponent only for desing report, but when we will need execute them in thread we can do someting like this (after loading report from database):

    frxReport1.GetDataSetList(myList);
    For item in myList do
    (frxReport1.GetDataset(item).Component as TIBQuery).Database := myDatabase;

    This is only a sketch - we will test it tommorow.
  • gpigpi
    edited 4:45AM
    If you use same database for all reports - try to use one TfrxIBXComponent in main app thread
  • edited April 2011
    gpi wrote: »
    If you use same database for all reports - try to use one TfrxIBXComponent in main app thread


    When reports are executed from main thread I use TfrxIBXComponent in main thread - then there is only one thread = main thread. When reports are executed in thread's (child threads of service application) I can not use same database for all of them - its forbidden (I believe You know why).

    I want to centralize all logic in the same unit (TDataModule). An application have one instance of this datamodule. This object is responsible for loading from DB / preparing / executing / printing / exporting reports. In service, all child thread create have own instance of this datamodule. Those threads can load from DB / prepare / execute report using the same code. I was working great, until we program our service to execute more than one report at exactly same time. That is how I found this problem.

    However - now I'm sure that this is not my mistake - thats FR desing issue. So thanks for help ! As I mention above, tomorrow I will try solution with GetDataSetList().
  • edited 4:45AM
    b0bik wrote: »
    I was working great, until we program our service to execute more than one report at exactly same time. That is how I found this problem.

    I tried to follow your idea about this application. I'm not sure if I understood everything well (the application run as an operating system service or end user decides when he wants it to run). But when you face such a problem - have you thought about making two twin applications? One main to do things as before, and second just to print reports in threads? Is this possible in your case to change the logic of data manipulation in that way?

    Mick
  • edited 4:45AM
    Mick.pl wrote: »
    I tried to follow your idea about this application. I'm not sure if I understood everything well (the application run as an operating system service or end user decides when he wants it to run).
    It's not one application - it's one Delphi unit used both in service and in application. There is over 90% common code.
    Mick.pl wrote: »
    But when you face such a problem - have you thought about making two twin applications?
    Yes I did. But it is easier for me and for users to have one concept of creating reports (without need to define connection). User can use such report in two ways - print it from application, or schedule that in a service.
    Of course there is a lot of {$IFDEF SRV}, so that souldn't be problem do add another one {$IFDEF} - in order to use frxIBXComponents only in application. If idea with GetDataSetList will work - I will be satisfying solution for me.

    Mick.pl wrote: »
    Is this possible in your case to change the logic of data manipulation in that way?
    Rather not. As i wrote before this mechanism is about two years old...
  • edited 4:45AM
    I forgot to ask - "Mick.pl" - is it possible that U are from Poland ?
  • edited 4:45AM
    Yes, I am [img]style_emoticons/<#EMO_DIR#>/smile.gif" style="vertical-align:middle" emoid=":)" border="0" alt="smile.gif" /> Mick[/img]
  • edited April 2011
    Mick.pl wrote: »
    Yes, I am [img]style_emoticons/<#EMO_DIR#>/smile.gif" style="vertical-align:middle" emoid=":)" border="0" alt="smile.gif" /> Mick[/img]

    Cool ( ;

    b
  • edited 4:45AM
    Have a look at your control panel on this forum - I've sent a message there for you [img]style_emoticons/<#EMO_DIR#>/smile.gif" style="vertical-align:middle" emoid=":)" border="0" alt="smile.gif" /> Mick[/img]

Leave a Comment

Rich Text Editor. To edit a paragraph's style, hit tab to get to the paragraph menu. From there you will be able to pick one style. Nothing defaults to paragraph. An inline formatting menu will show up when you select text. Hit tab to get into that menu. Some elements, such as rich link embeds, images, loading indicators, and error messages may get inserted into the editor. You may navigate to these using the arrow keys inside of the editor and delete them with the delete or backspace key.