Search code examples
multithreadingfiledelphiio

Need help for Delphi multithreaded file writing


I am writing a Delphi dll which calculates and writes the result to a CSV file. The calling program is multithreaded, so one problem is the simultaneous multiple writing to the file which causes the crash of the calling program. I tried to use the critical section to lock the file writing, but crashes still occur. If I configure the program to use only one thread, the problem disappears. Below is my code:

    library Question;

    uses
      SysUtils,
      Classes,
      Math,
      SyncObjs;

    {$R *.res}

    Var

        Outputfile: textfile;

        CriticalSection: TCriticalSection;

        CalNumb: integer = 0;

        PrintString: String;

    Threadvar

    Cal1, Cal2, Cal1Last: double;

    Function Calculator (input1, input2, input3, input4: double; 
    Factor: double; LastCal: Boolean; Print: integer): double stdcall;

    Const
        Divisor = 4;
    Var
        Temp: double;
    Begin

    Cal1Last:= Cal1;
    Cal1:= (input1+ input2+input3+ input4)/Divisor;
    Cal2:= (Cal1+Factor*Cal1Last)/2;
    Temp:= Cal2 - Cal1Last;

          If LastCal and (Print = 1) then
                begin

                  CriticalSection:= TCriticalSection.Create;
                  Try
                    Try
                      Inc(CalNumb);
                      Assign(Outputfile, 'C:\Calculator\Result.csv');
                      If FileExists('C:\Calculator\Result.csv') then 
                      Append(Outputfile) else rewrite (Outputfile);

                      If CalNumb = 1 then
                      begin
                        PrintString:= 'CalNumb' + ',' + 'Cal1' + ',' + 
                        'Cal1Last' + ',' + 'Cal2' + ',';

                        Writeln(Outputfile, PrintString);
                      end;


                      Writeln(Outputfile,
                      CalNumb, ',', Cal1:5:2, ',', Cal1Last:5:2, ',', Cal2:5:2, ',');


                    Finally
                      Close(Outputfile);
                    End;

                  Finally
                    CriticalSection.Free;
                  End;

                end;

    If Cal1 <> 0 then Calculator:= Temp/Cal1 else Calculator:= 0;

    End;

    Exports
           Calculator;

    begin
    end.

Are there any errors in my code? Multithread calculation and external file writing is essential for this project. Do you have any suggestion and comments? I am a beginner, so if you could post your code here, it will be a big help for me. Thank you very much in advance! //////////////////////////////////////////////////////////////////////////////////////////////////

Graymatter, thanks for your suggestion. After I make the changes you suggested, the application crashes before writing into the file. The previous version of dll can write certain lines of data into the file before the crash. In case I might make wrong changes, I post the changed part below. The other part of the code is not changed.

          If LastCal and (Print = 1) then
                begin

                  CriticalSection.Acquire;
                  Try
                    Try
                      Inc(CalNumb);
                      Assign(Outputfile, 'C:\Calculator\Result.csv');
                      If FileExists('C:\Calculator\Result.csv') then 
                      Append(Outputfile) else rewrite (Outputfile);

                      If CalNumb = 1 then
                      begin
                        PrintString:= 'CalNumb' + ',' + 'Cal1' + ',' + 
                        'Cal1Last' + ',' + 'Cal2' + ',';

                        Writeln(Outputfile, PrintString);
                      end;


                      Writeln(Outputfile,
                      CalNumb, ',', Cal1:5:2, ',', Cal1Last:5:2, ',', Cal2:5:2, ',');


                    Finally
                      Close(Outputfile);
                    End;

                  Finally
                    CriticalSection.Release;
                  End;

                end;

    If Cal1 <> 0 then Calculator:= Temp/Cal1 else Calculator:= 0;

    End;

    Exports
           Calculator;

    begin
    end.


    initialization
      CriticalSection := TCriticalSection.Create;
    finalization
      CriticalSection.Free;
    end;

Solution

  • Creating a critical section object doesn't do any locking. You need to call Acquire to get the lock and Release to release the lock.

    See Using Critical Sections in the online help.

    The problem is that the critical section needs to be created when the DLL is loaded so you will need to do something like this:

    begin
      ...
      CriticalSection.Acquire;
      try
        ...
        // The code that needs to be locked goes inside here.
        // In your case it would be the code that opens the file
        // and appends the data.
        ...
      finally
        CriticalSection.Release;
      end;
      ...
    end;
    

    You will have to move your code into a separate unit so you can include initialization and finalization blocks. These blocks will get executed when the DLL is first loaded and when it's unloaded respectively.

    Your new unit would look like this:

    unit UnitForDLL;
    
    interface
    
    function Calculator(input1, input2, input3, input4: double; 
      Factor: double; LastCal: Boolean; Print: integer): double; stdcall;
    
    implementation
    
    uses
      SyncObjs, SysUtils;
    
    var
      ...
      CriticalSection: TCriticalSection;
      ...
    
    function Calculator(input1, input2, input3, input4: double; 
      Factor: double; LastCal: Boolean; Print: integer): double; stdcall;
    begin
      ...
      CriticalSection.Acquire;
      try
        ...
        // The code that needs to be locked goes inside here.
        // In your case it would be the code that opens the file
        // and appends the data.
        ...
      finally
        CriticalSection.Release;
      end;
      ...
    end;
    
    initialization
      CriticalSection := TCriticalSection.Create;
    finalization
      CriticalSection.Free;
    end.
    

    Once you have done that your project itself will be changed to a much simpler:

    library Question;
    
    uses
      SysUtils,
      Classes,
      UnitForDLL;
    
    {$R *.res}
    
    exports
           Calculator;
    
    begin
    end.