2014-11-29 3 views
0

CriticalSection을 사용하여 텍스트 파일에 스레드 안전 로그를 작성하기위한 클래스를 만들었습니다. 내가 크리티컬 섹션과 멀티 스레딩 프로그램 (... 델파이), 나는 확실히 뭔가 잘못하고 있어요의 전문가가 아니다Delphi 멀티 스레딩 파일 쓰기 : I/O 오류 32

...

unit ErrorLog; 

interface 

uses 
    Winapi.Windows, System.SysUtils; 

type 
    TErrorLog = class 
    private 
     FTextFile : TextFile; 
     FLock  : TRTLCriticalSection; 
    public 
     constructor Create(const aLogFilename:string); 
     destructor Destroy; override; 
     procedure Write(const ErrorText: string); 
    end; 

implementation 


constructor TErrorLog.Create(const aLogFilename:string); 
begin 
    inherited Create; 

    InitializeCriticalSection(FLock); 

    AssignFile(FTextFile, aLogFilename); 

    if FileExists(aLogFilename) then 
    Append(FTextFile) 
    else 
    Rewrite(FTextFile); 
end; 


destructor TErrorLog.Destroy; 
const 
    fmTextOpenWrite = 55218; 
begin 
    EnterCriticalSection(FLock); 
    try 
     if TTextRec(FTextFile).Mode <> fmTextOpenWrite then 
     CloseFile(FTextFile); 

     inherited Destroy; 
    finally 
     LeaveCriticalSection(FLock); 
     DeleteCriticalSection(FLock); 
    end; 
end; 


procedure TErrorLog.Write(const ErrorText: string); 
begin 
    EnterCriticalSection(FLock); 

    try 
    WriteLn(FTextFile, ErrorText); 
    finally 
    LeaveCriticalSection(FLock); 
    end; 
end; 

end. 

내가 함께 양식을 작성한 클래스를 테스트하기 타이머는 100 밀리 초로 설정 : 로그가 기록되는

procedure TForm1.Timer1Timer(Sender: TObject); 
var 
    I : integer; 
    aErrorLog : TErrorLog; 
begin 
    aErrorLog := nil; 
    for I := 0 to 1000 do begin 
    try 
     aErrorLog := TErrorLog.Create(FormatDateTime('ddmmyyyy', Now) + '.txt'); 
     aErrorLog.Write('new line'); 
    finally 
     if Assigned(aErrorLog) then FreeAndNil(aErrorLog); 
    end; 
    end; 
end; 

하지만 때때로

(아마도 때문에 다른 스레드에서 사용) CloseFile(FTextFile)I/O Error 32 예외를 발생3210

어디에서 잘못 했습니까?

UPDATE :

모든 의견과 나는 완전히 변화된 접근 방식을 가지고있는 답변을 읽은 후. 내 솔루션을 공유합니다.

ThreadUtilities.pas

(* Implemented for Delphi3000.com Articles, 11/01/2004 
     Chris Baldwin 
     Director & Chief Architect 
     Alive Technology Limited 
     http://www.alivetechnology.com 
*) 
unit ThreadUtilities; 

interface 

uses Windows, SysUtils, Classes; 

type 
    EThreadStackFinalized = class(Exception); 
    TSimpleThread = class; 

    // Thread Safe Pointer Queue 
    TThreadQueue = class 
    private 
     FFinalized: Boolean; 
     FIOQueue: THandle; 
    public 
     constructor Create; 
     destructor Destroy; override; 
     procedure Finalize; 
     procedure Push(Data: Pointer); 
     function Pop(var Data: Pointer): Boolean; 
     property Finalized: Boolean read FFinalized; 
    end; 

    TThreadExecuteEvent = procedure (Thread: TThread) of object; 

    TSimpleThread = class(TThread) 
    private 
     FExecuteEvent: TThreadExecuteEvent; 
    protected 
     procedure Execute(); override; 
    public 
     constructor Create(CreateSuspended: Boolean; ExecuteEvent: TThreadExecuteEvent; AFreeOnTerminate: Boolean); 
    end; 

    TThreadPoolEvent = procedure (Data: Pointer; AThread: TThread) of Object; 

    TThreadPool = class(TObject) 
    private 
     FThreads: TList; 
     FThreadQueue: TThreadQueue; 
     FHandlePoolEvent: TThreadPoolEvent; 
     procedure DoHandleThreadExecute(Thread: TThread); 
    public 
     constructor Create(HandlePoolEvent: TThreadPoolEvent; MaxThreads: Integer = 1); virtual; 
     destructor Destroy; override; 
     procedure Add(const Data: Pointer); 
    end; 

implementation 

{ TThreadQueue } 

constructor TThreadQueue.Create; 
begin 
    //-- Create IO Completion Queue 
    FIOQueue := CreateIOCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); 
    FFinalized := False; 
end; 

destructor TThreadQueue.Destroy; 
begin 
    //-- Destroy Completion Queue 
    if (FIOQueue <> 0) then 
     CloseHandle(FIOQueue); 
    inherited; 
end; 

procedure TThreadQueue.Finalize; 
begin 
    //-- Post a finialize pointer on to the queue 
    PostQueuedCompletionStatus(FIOQueue, 0, 0, Pointer($FFFFFFFF)); 
    FFinalized := True; 
end; 

(* Pop will return false if the queue is completed *) 
function TThreadQueue.Pop(var Data: Pointer): Boolean; 
var 
    A: Cardinal; 
    OL: POverLapped; 
begin 
    Result := True; 

    if (not FFinalized) then 
    //-- Remove/Pop the first pointer from the queue or wait 
     GetQueuedCompletionStatus(FIOQueue, A, ULONG_PTR(Data), OL, INFINITE); 

    //-- Check if we have finalized the queue for completion 
    if FFinalized or (OL = Pointer($FFFFFFFF)) then begin 
     Data := nil; 
     Result := False; 
     Finalize; 
    end; 
end; 

procedure TThreadQueue.Push(Data: Pointer); 
begin 
    if FFinalized then 
     Raise EThreadStackFinalized.Create('Stack is finalized'); 
    //-- Add/Push a pointer on to the end of the queue 
    PostQueuedCompletionStatus(FIOQueue, 0, Cardinal(Data), nil); 
end; 

{ TSimpleThread } 

constructor TSimpleThread.Create(CreateSuspended: Boolean; 
    ExecuteEvent: TThreadExecuteEvent; AFreeOnTerminate: Boolean); 
begin 
    FreeOnTerminate := AFreeOnTerminate; 
    FExecuteEvent := ExecuteEvent; 
    inherited Create(CreateSuspended); 
end; 

procedure TSimpleThread.Execute; 
begin 
    if Assigned(FExecuteEvent) then 
     FExecuteEvent(Self); 
end; 

{ TThreadPool } 

procedure TThreadPool.Add(const Data: Pointer); 
begin 
    FThreadQueue.Push(Data); 
end; 

constructor TThreadPool.Create(HandlePoolEvent: TThreadPoolEvent; 
    MaxThreads: Integer); 
begin 
    FHandlePoolEvent := HandlePoolEvent; 
    FThreadQueue := TThreadQueue.Create; 
    FThreads := TList.Create; 
    while FThreads.Count < MaxThreads do 
     FThreads.Add(TSimpleThread.Create(False, DoHandleThreadExecute, False)); 
end; 

destructor TThreadPool.Destroy; 
var 
    t: Integer; 
begin 
    FThreadQueue.Finalize; 
    for t := 0 to FThreads.Count-1 do 
     TThread(FThreads[t]).Terminate; 
    while (FThreads.Count > 0) do begin 
     TThread(FThreads[0]).WaitFor; 
     TThread(FThreads[0]).Free; 
     FThreads.Delete(0); 
    end; 
    FThreadQueue.Free; 
    FThreads.Free; 
    inherited; 
end; 

procedure TThreadPool.DoHandleThreadExecute(Thread: TThread); 
var 
    Data: Pointer; 
begin 
    while FThreadQueue.Pop(Data) and (not TSimpleThread(Thread).Terminated) do begin 
     try 
      FHandlePoolEvent(Data, Thread); 
     except 
     end; 
    end; 
end; 

end. 

ThreadFileLog.pas

(* From: http://delphi.cjcsoft.net/viewthread.php?tid=45763 *) 
unit ThreadFileLog; 

interface 

uses Windows, ThreadUtilities, System.Classes; 

type 
    PLogRequest = ^TLogRequest; 
    TLogRequest = record 
     LogText : String; 
     FileName : String; 
    end; 

    TThreadFileLog = class(TObject) 
    private 
     FThreadPool: TThreadPool; 
     procedure HandleLogRequest(Data: Pointer; AThread: TThread); 
    public 
     constructor Create(); 
     destructor Destroy; override; 
     procedure Log(const FileName, LogText: string); 
    end; 

implementation 

uses 
    System.SysUtils; 

(* Simple reuse of a logtofile function for example *) 
procedure LogToFile(const FileName, LogString: String); 
var 
    F: TextFile; 
begin 
    AssignFile(F, FileName); 

    if not FileExists(FileName) then 
     Rewrite(F) 
    else 
     Append(F); 

    try 
     Writeln(F, LogString); 
    finally 
     CloseFile(F); 
    end; 
end; 

constructor TThreadFileLog.Create(); 
begin 
    FThreadPool := TThreadPool.Create(HandleLogRequest, 1); 
end; 

destructor TThreadFileLog.Destroy; 
begin 
    FThreadPool.Free; 
    inherited; 
end; 

procedure TThreadFileLog.HandleLogRequest(Data: Pointer; AThread: TThread); 
var 
    Request: PLogRequest; 
begin 
    Request := Data; 
    try 
     LogToFile(Request^.FileName, Request^.LogText); 
    finally 
     Dispose(Request); 
    end; 
end; 

procedure TThreadFileLog.Log(const FileName, LogText: string); 
var 
    Request: PLogRequest; 
begin 
    New(Request); 
    Request^.LogText := LogText; 
    Request^.FileName := FileName; 
    FThreadPool.Add(Request); 
end; 

end. 

기본 형태의 예를

unit Unit1; 

interface 

uses 
    Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, 
    Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, 
    Vcl.StdCtrls, ThreadFileLog; 

type 
    TForm1 = class(TForm) 
    BtnStart: TButton; 
    procedure FormCreate(Sender: TObject); 
    procedure FormDestroy(Sender: TObject); 
    procedure BtnStartClick(Sender: TObject); 
    private 
    FThreadFileLog : TThreadFileLog; 
    end; 

var 
    Form1: TForm1; 

implementation 

{$R *.dfm} 

procedure TForm1.BtnStartClick(Sender: TObject); 
var 
I : integer; 
aNow : TDateTime; 
begin 
    aNow := Now; 

    for I := 0 to 500 do 
     FThreadFileLog.Log(
     FormatDateTime('ddmmyyyyhhnn', aNow) + '.txt', 
     FormatDateTime('dd-mm-yyyy hh:nn:ss.zzz', aNow) + ': I: ' + I.ToString 
    ); 

    ShowMessage('logs are performed!'); 
end; 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    FThreadFileLog := TThreadFileLog.Create(); 
end; 

procedure TForm1.FormDestroy(Sender: TObject); 
begin 
    FThreadFileLog.Free; 

    ReportMemoryLeaksOnShutdown := true; 
end; 




end. 

출력 로그 :

대신 당신이 당신의 파일을 폐쇄 여부를 확인해야하며,이 아닌 경우 당신이 그것을 닫습니다 폐쇄 TTextRec(FTextFile).Mode <> fmTextOpenWrite을 확인하는
30-11-2014 14.01.13.252: I: 0 
30-11-2014 14.01.13.252: I: 1 
30-11-2014 14.01.13.252: I: 2 
30-11-2014 14.01.13.252: I: 3 
30-11-2014 14.01.13.252: I: 4 
30-11-2014 14.01.13.252: I: 5 
30-11-2014 14.01.13.252: I: 6 
30-11-2014 14.01.13.252: I: 7 
30-11-2014 14.01.13.252: I: 8 
30-11-2014 14.01.13.252: I: 9 
... 
30-11-2014 14.01.13.252: I: 500 
+1

귀하의 테스트가 멀티 스레드하지 않습니다. 따라서 많은 테스트가 아닙니다. AV를 비활성화하십시오. 또한, 왜 아직도 파스칼 I/O를 사용하고 있습니까? –

+0

자, 어떻게하면 멀티 스레드 환경에서 테스트 할 수 있습니까? 예외가 발생하지 않는 한 AV를 사용 중지했습니다. 어떤 파스칼 I/O : D? –

+1

적절한 테스트에는 다중 스레드와 로그 클래스의 단일 인스턴스가 있습니다. 왜 새로운 인스턴스를 많이 만들고 있습니까? 당신의 try/finally 패턴은 객체의 수명이 나쁘다. 그걸 똑바로해야 해. –

답변

7

. 이 코드로 언급 검사를 대체

시도 :

if TTextRec(FTextFile).Mode <> fmClosed then 
    CloseFile(FTextFile); 

을 편집 :

이 파일을 잠금 바이러스 백신과는 아무 상관이 없습니다. 이것은 소멸자의 단순한 실수입니다.

파일이 이미 열려 쓰기 모드에서 열립니다, 열려 쓰기 모드로 하지 경우에만 원래 코드는 파일을 닫는 - 그래서 파일을 닫는 결코입니다.

여기에서 실수가 발생한 위치를 설명합니다.

로거 클래스의 전체 디자인은 다음과 같습니다. 이것은 질문이 아니었고 질문은 간단했으며 간단하고 효과적인 해결책을 제공했습니다.

시몬 (Simone)이 우리에게 로거 수업을 설계하는 방법을 가르치기를 원한다면 그는 그것을 요구할 것이라고 생각합니다.

+0

보고 내용을 어떻게 설명합니까? –

+0

@DavidHeffernan으로 충분합니까? – Wodzu

+0

그게 더 좋습니다. 지금은 이해. 답변을 더 잘 해주셔서 감사합니다. 개인 콘텐츠가 적 으면 더 좋을 것입니다. –

2

여러 스레드가 로그 파일에 쓸 수있는 오류 로그 클래스가 필요한 경우 중요한 섹션을 사용하여 쓰기 메서드를 보호하는 것이 올바른 방법입니다.

이제 응용 프로그램에서 이러한 오류 로깅 개체 중 하나만 인스턴스화하므로 임계 섹션을 사용하여 소멸자 메서드를 보호 할 필요가 없습니다.

오류 로그 파일의 위치는 응용 프로그램 데이터 폴더에 있어야합니다.

의 I/O 오류 (32)는이 공유 위반에 대한 The process cannot access the file because it is being used by another process.

이유는 응용 프로그램 또는 외부 응용 프로그램에서이 될 수 있습니다. 응용 프로그램 디렉터리에 쓰기를 수행하면 일부 바이러스 백신 보호가 트리거 될 수 있습니다. 또는 응용 프로그램이 파일 모드가 다른 여러 위치에서 파일을 열어 두었습니다.

귀하의 테스트는 여러 가지 방법으로 결함이 :

  • 응용 프로그램 시작시에 한 번만 오류 로그 클래스를 인스턴스화하고 응용 프로그램을 닫을 때 그것을 파괴한다.
  • 타이머 이벤트 내의 다중 반복이 아닌 다른 스레드에서 오류 로그에 기록하십시오.
  • 타이머 이벤트는 짧은 시간 동안 만 프로그램 시퀀스를 실행해야합니다.
  • 시도는/마지막 순서는 다음과 같이 구성되어있다 :

    anObject := TObject.Create; 
    try 
        // Do something with anObject 
    finally 
        anObject.Free; 
    end; 
    
+1

모든 점을 고려하여, 이것이 어떻게 원래의 문제를 해결하는 데 도움이되는지는 알지 못한다. Answer에는 많은 추측이 있지만 문제에 대한 명확한 해결책을 제시하지는 않습니다. 질문은 로거 클래스를 설계하는 방법이나 올바른 pathers 등이 아니 었습니다. @Simone은 왜 I/O 오류가 발생하는지 묻고있었습니다. 이 대답은 여전히 ​​그에게이 문제를 남깁니다. – Wodzu

+0

@ Wodzu 질문을 해결하려고 시도 할뿐 아니라이 모든 것을 지적하는 것이 중요합니다. –

+2

@ Wodzu, 내 대답은 그럴듯한 대답과 코드를 개선하기위한 몇 가지 포인트를 모두 보유하고 있다고 생각합니다. 당신의 대답은 잘 잡힌 직접적인 이유입니다. –