-3

私のアプリケーションでは、テキスト ファイル (ログ、トレースなど) を書き込むときにTFileStreamクラスを使用します。マルチスレッド環境でデータを書き込む場合がありますが、その手順は次のとおりです。

1- キャッシュ データの書き込み
2- 1000 行ごとにファイルに保存します。
3- データを消去します。

このプロセスは、すべての処理中に繰り返されます。

問題の説明:

16 スレッドの場合、システムは次の例外をスローします。

アクセス違反 - ファイルは別のアプリケーションで既に使用されています。
別のスレッドを開く必要があるときに、あるスレッドで使用されているハンドルがまだ閉じられていないために、これが発生していると思います。

アーキテクチャを次のように変更しました: (以下は新しい実装です)
以前の方法では、TFileStream は FileName および Mode パラメータで作成され、ハンドルを閉じて破棄されました (私は TMyFileStream を使用していませんでした)。

TMyFileStream = class(TFileStream)
public
   destructor Destroy; override;
end;

TLog = class(TStringList)
private
  FFileHandle: Integer;
  FirstTime: Boolean;
  FName: String;
protected
  procedure Flush;
  constructor Create;
  destructor Destroy;
end; 


destructor TMyFileStream.Destroy;
begin
  //Do Not Close the Handle, yet!
  FHandle := -1;
  inherited Destroy;
end;

procedure TLog.Flush;
var
  StrBuf: PChar; LogFile: string;
  F: TFileStream;
  InternalHandle: Cardinal;
begin
  if (Text <> '') then
  begin
    LogFile:= GetDir() + FName + '.txt';
    ForceDirectories(ExtractFilePath(LogFile));
    if FFileHandle < 0 then
    begin
      if FirstTime then
        FirstTime := False;

      if FileExists(LogFile) then
        if not SysUtils.DeleteFile(LogFile) then
          RaiseLastOSError;

      InternalHandle := CreateFile(PChar(LogFile), GENERIC_READ or GENERIC_WRITE,         FILE_SHARE_READ, nil, CREATE_NEW, 0,0);
      if InternalHandle = INVALID_HANDLE_VALUE then
        RaiseLastOSError
      else if GetLastError = ERROR_ALREADY_EXISTS then
      begin
        InternalHandle := CreateFile(PChar(LogFile), GENERIC_READ   or GENERIC_WRITE, FILE_SHARE_READ, nil, OPEN_EXISTING, 0,0);
        if InternalHandle = INVALID_HANDLE_VALUE then
          RaiseLastOSError
        else
          FFileHandle := InternalHandle;
      end
      else
        FFileHandle := InternalHandle;
    end;

    F := TMyFileStream.Create(FFileHandle);
    try
      StrBuf := PChar(Text);
      F.Position := F.Size;
      F.Write(StrBuf^, StrLen(StrBuf));
    finally
      F.Free();
    end;

    Clear;
  end;
end;

destructor TLog.Destroy;
begin
  FUserList:= nil;
  Flush;
  if FFileHandle >= 0 then
    CloseHandle(FFileHandle);
  inherited;
end;

constructor TLog.Create;
begin
  inherited;      
  FirstTime := True;      
  FFileHandle := -1;
end;

別のより良い方法はありますか?
この実装は正しいですか?
これを改善してもいいですか?
ハンドルについての私の推測は正しかったですか?

すべての広告が同じ Log オブジェクトを使用します。

再入場はありません、チェックしました!TFileStream に問題があります。

Add へのアクセスは同期、つまりクリティカル セッションを使用し、1000 行に達すると Flush プロシージャが呼び出されます。

PS: サードパーティ製のコンポーネントは必要ありません。自分で作成したいのです。

4

5 に答える 5

1

まあ、まず、意味がありませんTMyFileStream。あなたが探しているのは ですTHandleStream。このクラスを使用すると、ライフタイムを制御するファイル ハンドルを指定できます。を使用THandleStreamすると、バリアントの厄介なハッキングを回避できます。とはいえ、なぜストリームを気にしているのですか? ストリームを作成して使用するコードをSetFilePointer、ファイルの末尾をシークするための への呼び出しと、 WriteFileコンテンツを書き込むための への呼び出しに置き換えます。

ただし、それを使用しても、提案されたソリューションにはさらに同期が必要です。単一の Windows ファイル ハンドルは、同期せずに複数のスレッドから同時に使用することはできません。ファイルの書き込みをシリアル化していることをコメントでほのめかします(質問にあるはずです)。もしそうなら、あなたは大丈夫です。

于 2013-05-14T13:08:13.597 に答える
0

どうですか:

各スレッドで、lines.count=1000 になるまでログ行を TStringList インスタンスに追加します。次に、TStringList をブロッキング プロデューサー/コンシューマー キューにプッシュし、すぐに新しい TStringList を作成して、新しいリストへのロギングを続行します。

TStringList インスタンスをデキューし、ファイルに書き込み、解放する 1 つのログ スレッドを使用します。

これにより、ログの書き込みがディスク/ネットワークの遅延から分離され、危険なファイル ロックへの依存がなくなり、実際に確実に動作します。

于 2013-05-14T13:13:26.843 に答える
-1

単一のファイルに書き込む必要があるマルチスレッド コードがある場合は、できる限り制御できるようにすることをお勧めします。つまり、どのように機能するか 100% 確信が持てないクラスは避けてください。

各スレッドがロガー オブジェクトへの参照を持ち、それに文字列を追加する複数のスレッド > 単一のロガー アーキテクチャを使用することをお勧めします。1000 行に達すると、ロガーは収集したデータをファイルにフラッシュします。

  • TFileStream を使用してファイルにデータを書き込む必要はありません。David が既に提案したように、CreateFile()/SetFilePointer()/WriteFile() を使用できます。
  • TStringList はスレッドセーフではないため、ロックを使用する必要があります

main.dpr :

{$APPTYPE CONSOLE}

uses
  uLogger,
  uWorker;

const
  WORKER_COUNT = 16;

var
  worker: array[0..WORKER_COUNT - 1] of TWorker;
  logger: TLogger;
  C1    : Integer;

begin
  Write('Creating logger...');
  logger := TLogger.Create('test.txt');
  try
    WriteLn(' OK');
    Write('Creating threads...');
    for C1 := Low(worker) to High(worker) do
    begin
      worker[C1] := TWorker.Create(logger);
      worker[C1].Start;
    end;
    WriteLn(' OK');

    Write('Press ENTER to terminate...');
    ReadLn;

    Write('Destroying threads...');
    for C1 := Low(worker) to High(worker) do
    begin
      worker[C1].Terminate;
      worker[C1].WaitFor;
      worker[C1].Free;
    end;
    WriteLn(' OK');
  finally
    Write('Destroying logger...');
    logger.Free;
    WriteLn(' OK');
  end;
end.

uWorker.pas :

unit uWorker;

interface

uses
  System.Classes, uLogger;

type
  TWorker = class(TThread)
  private
    FLogger: TLogger;

  protected
    procedure Execute; override;

  public
    constructor Create(const ALogger: TLogger);
    destructor Destroy; override;
  end;

implementation


function RandomStr: String;
var
  C1: Integer;
begin
  result := '';
  for C1 := 10 to 20 + Random(50) do
    result := result + Chr(Random(91) + 32);
end;


constructor TWorker.Create(const ALogger: TLogger);
begin
  inherited Create(TRUE);

  FLogger := ALogger;
end;

destructor TWorker.Destroy;
begin
  inherited;
end;

procedure TWorker.Execute;
begin
  while not Terminated do
    FLogger.Add(RandomStr);
end;

end.

uLogger.pas :

unit uLogger;

interface

uses
  Winapi.Windows, System.Classes;

type
  TLogger = class
  private
    FStrings        : TStringList;
    FFileName       : String;
    FFlushThreshhold: Integer;
    FLock           : TRTLCriticalSection;

    procedure LockList;
    procedure UnlockList;
    procedure Flush;
  public
    constructor Create(const AFile: String; const AFlushThreshhold: Integer = 1000);
    destructor Destroy; override;

    procedure Add(const AString: String);

    property FlushThreshhold: Integer read FFlushThreshhold write FFlushThreshhold;
  end;

implementation

uses
  System.SysUtils;

constructor TLogger.Create(const AFile: String; const AFlushThreshhold: Integer = 1000);
begin
  FFileName := AFile;
  FFlushThreshhold := AFlushThreshhold;
  FStrings := TStringList.Create;

  InitializeCriticalSection(FLock);
end;

destructor TLogger.Destroy;
begin
  FStrings.Free;
  DeleteCriticalSection(FLock);

  inherited;
end;

procedure TLogger.LockList;
begin
  EnterCriticalSection(FLock);
end;

procedure TLogger.UnlockList;
begin
  LeaveCriticalSection(FLock);
end;

procedure TLogger.Add(const AString: String);
begin
  LockList;
  try
    FStrings.Add(AString);
    if FStrings.Count >= FFlushThreshhold then
      Flush;
  finally
   UnlockList;
  end;
end;

procedure TLogger.Flush;
var
  strbuf  : PChar;
  hFile   : THandle;
  bWritten: DWORD;
begin
  hFile := CreateFile(PChar(FFileName), GENERIC_WRITE, FILE_SHARE_READ, nil, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
  try
    strbuf := PChar(FStrings.Text);
    SetFilePointer(hFile, 0, nil, FILE_END);
    WriteFile(hFile, strbuf^, StrLen(strbuf), bWritten, nil);
    FStrings.Clear;
  finally
    CloseHandle(hFile);
  end;
end;

end.
于 2013-05-14T14:18:01.907 に答える