【问题标题】:Need help for Delphi multithreaded file writingDelphi多线程文件编写需要帮助
【发布时间】:2015-11-12 06:23:08
【问题描述】:

我正在编写一个 Delphi dll,它计算并将结果写入 CSV 文件。调用程序是多线程的,所以一个问题是同时多次写入文件导致调用程序崩溃。我尝试使用临界区锁定文件写入,但仍然发生崩溃。如果我将程序配置为只使用一个线程,问题就会消失。以下是我的代码:

    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.

我的代码有错误吗?多线程计算和外部文件写入对于这个项目是必不可少的。你有什么建议和cmets?我是初学者,所以如果你可以在这里发布你的代码,这对我来说将是一个很大的帮助。非常感谢您! ///////////////////////////////////////// //////////////////////////////////////////////// p>

Graymatter,感谢您的建议。在我做出您建议的更改后,应用程序在写入文件之前崩溃。以前版本的dll可以在崩溃前将某些行数据写入文件中。如果我可能做出错误的更改,我会在下面发布更改的部分。其他部分代码不变。

          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;

【问题讨论】:

  • 您是否记录/调试过您的代码,您的代码的哪一行导致应用程序崩溃?:
  • 当我注释文件分配,追加和写入块时,应用程序崩溃消失了。
  • 在每个线程中写入单独的文件并在所有线程完成后将它们连接到最终文件中可能会更有效。
  • TOndrej,你的建议很棒。但是,我只能修改dll,不能修改应用程序。如何修改分配部分,以便每个线程可以写入不同的文件?似乎我无法将变量放入文件名或路径中。我知道它可以通过一些复杂的方式来完成。你能详细说明一下吗?谢谢!
  • 作为一个初学者,你咬的比你能嚼的多。从您说您是多线程的事实中可以看出这一点,但根本没有显示与线程相关的任何内容。您正在创建的这些线程在哪里?

标签: multithreading file delphi io writing


【解决方案1】:

创建临界区对象不会进行任何锁定。您需要调用Acquire 获取锁,调用Release 释放锁。

请参阅在线帮助中的Using Critical Sections

问题是在加载 DLL 时需要创建临界区,因此您需要执行以下操作:

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;

您必须将代码移动到一个单独的单元中,以便您可以包含 initializationfinalization 块。这些块将分别在第一次加载和卸载 DLL 时执行。

您的新单位将如下所示:

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.

一旦你这样做了,你的项目本身就会变得更简单:

library Question;

uses
  SysUtils,
  Classes,
  UnitForDLL;

{$R *.res}

exports
       Calculator;

begin
end.

【讨论】:

  • 感谢您的回答!我进行了您建议的更改,但应用程序仍然以不同的方式崩溃。请参阅更新的问题。谢谢!
  • @BF2015 您没有包含初始化和完成部分,所以它崩溃了,因为您正在访问一个尚未创建的对象。
  • @BF2015 我已经更新了答案,让您更深入地了解您需要做什么。
  • @whosrdaddy 这取决于。如果线程是在主机中创建的,并且所有函数都调用同一个 DLL,那就没问题了。但是这个问题无法回答,而且一旦解决了一个问题,提问者就改变了问题也无济于事。最好的策略是投反对票、关闭和删除问题,并训练提问者提出真正的问题。
  • 事情并非如此。问题发生了变化,并开始了修复一个错误的路径,然后将下一个错误编辑到问题中。这不是该网站的目的。您应该已经明确了线程,这样我们就不必猜测线程来自何处。而且您应该将代码缩减为一个简单的 MCVE。这就是学习的方法。请接受答案。