2017-05-26 6 views
2

このコードがネット上に見つかりましたが、動作していますが、メインスレッドの変数を別のスレッドから直接読み取ることができればわかりません。この例では、フラグ(変数)はで、CancelCopyです。 一般的に、変数の状態を他のスレッドのメインスレッドから、ただちに待たずに読むことができます。メインスレッドフラグを他のスレッドから安全に確認するにはどうしたらいいですか?

type 
    TCopyEx = packed record 
    Source: String; 
    Dest: String; 
    Handle: THandle; 
    end; 
    PCopyEx = ^TCopyEx; 

const 
    CFEX_CANCEL   = WM_USER + 1; 

var 
    CancelCopy:Boolean=False; 

function CopyFileProgress(TotalFileSize, TotalBytesTransferred, StreamSize, 
    StreamBytesTransferred: LARGE_INTEGER; dwStreamNumber, dwCallbackReason: DWORD; 
    hSourceFile, hDestinationFile: THandle; lpData: Pointer): DWORD; stdcall; 
begin 
if CancelCopy then begin 
    SendMessage(THandle(lpData), CFEX_CANCEL, 0, 0); 
    result:=PROGRESS_CANCEL; 
    Exit; 
end; 
//rest of the code here....... 
end; 

function CopyExThread(p: PCopyEx):Integer; 
var 
    Source: String; 
    Dest: String; 
    Handle: THandle; 
    Cancel: PBool; 
begin 
Source:=p.Source; 
Dest:=p.Dest; 
Handle:=p.Handle; 
Cancel:=PBOOL(False); 
CopyFileEx(PChar(Source), PChar(Dest), @CopyFileProgress, Pointer(Handle), Cancel, COPY_FILE_NO_BUFFERING); 
Dispose(p); 
result:=0; 
end; 

procedure TFormMain.ButtonCopyClick(Sender: TObject); 
var 
    Params: PCopyEx; 
    ThreadID: Cardinal; 
begin 
    cancelCopy := False; 
    New(Params); 
    Params.Source := EditOriginal.Text; 
    Params.Dest := EditCopied.Text; 
    Params.Handle := Handle; 
    CloseHandle(BeginThread(nil, 0, @CopyExThread, Params, 0, ThreadID)); 
end; 

procedure TFormMain.ButtonCancelClick(Sender: TObject); 
begin 
    cancelCopy := true; 
end; 
+2

それは結構です。データ競争の可能性がありますが、それは良性です。 –

+0

スレッドは読んでいるだけなので、 –

+0

あなたは「スレッド」と言っていますが、2つのスレッドがあります。あなたがメインであることを知っているかもしれないし、ワーカーであることを知っているかもしれないが、システムはただ2つのスレッドを見る。 1つのスレッドがその変数を読み取り、もう1つのスレッドがその変数を書き込みます。それはデータ競争です。しかし、レースは良性です。 –

答えて

2

技術的には、表示されたコードは問題なく、正常に動作します。

ただし、小さな間違いがあります。間違ったポインタ値をCopyFileEx()pbCancelパラメータに渡しています。しかし、あなたが渡しているポインタが実質的にnilに設定されていて、pbCancelnilポインタを受け付けるので、コードがクラッシュしないので、CopyFileEx()はそのパラメータを無視します。あなたが行うことにをを想定している何

は、あなたがコピーをキャンセルする任意の時点でTRUEに設定することができBOOL変数のアドレスを渡しています。 CopyFileEx()はその変数を監視しますが、変数が設定されているときにコールバックからPROGRESS_CANCELを手動で返す必要はありません(戻り値PROGRESS_CANCEL、コールバックにコピー自体に関連しないエラーが発生し、結果としてコピーを中断したい場合エラーの)。しかし、私はグローバル変数を使用しません。私はコピーを実行しているフォームにローカルな変数を使用します。 - あなたがスレッドにTForm.HandleプロパティからHWNDを渡していると述べたことで

type 
    TFormMain = class(TForm) 
    ... 
    private 
    CancelCopy: BOOL; // <-- BOOL, not Boolean 
    ... 
    end; 

... 

type 
    TCopyEx = record 
    Source: String; 
    Dest: String; 
    Handle: HWND; 
    PCancelCopy: PBOOL; 
    end; 
    PCopyEx = ^TCopyEx; 

const 
    CFEX_CANCEL = WM_USER + 1; 

function CopyFileProgress(TotalFileSize, TotalBytesTransferred, StreamSize, 
    StreamBytesTransferred: LARGE_INTEGER; dwStreamNumber, dwCallbackReason: DWORD; 
    hSourceFile, hDestinationFile: THandle; lpData: Pointer): DWORD; stdcall; 
begin 
    // no need to watch CancelCopy here... 
    // do normal status handling here as needed... 
    // use PCopyEx(lpData)^ as needed... 
end; 

function CopyExThread(p: PCopyEx): Integer; 
begin 
    try 
    if not CopyFileEx(PChar(p.Source), PChar(p.Dest), @CopyFileProgress, p, p.PCancelCopy, COPY_FILE_NO_BUFFERING) then 
    begin 
     if GetLastError() = ERROR_REQUEST_ABORTED then 
     SendMessage(p.Handle, CFEX_CANCEL, 0, 0); 
    end; 
    finally 
    Dispose(p); 
    end; 
    Result := 0; 
end; 

procedure TFormMain.ButtonCopyClick(Sender: TObject); 
var 
    Params: PCopyEx; 
    ThreadID: Cardinal; 
begin 
    New(Params); 
    Params.Source := EditOriginal.Text; 
    Params.Dest := EditCopied.Text; 
    Params.Handle := Handle; 
    Params.PCancelCopy := @CancelCopy; // <-- pass address of CancelCopy here... 

    CancelCopy := FALSE; 
    CloseHandle(BeginThread(nil, 0, @CopyExThread, Params, 0, ThreadID)); 
end; 

procedure TFormMain.ButtonCancelClick(Sender: TObject); 
begin 
    CancelCopy := TRUE; 
end; 

、に注意する何か他のもの:

は、より多くの代わりにこのような何かを試してみてください。 TFormが何らかの理由でHWNDを破壊/再作成すると、スレッドがまだ実行されている間に、TCopyEx.Handleの値が無効なウィンドウを指し示すことになります(または悪いことに古いものを再利用する新しいウィンドウHWND値)。一般的に

TWinControl.Handleプロパティは、スレッドセーフではありません、そのためだけではその理由、あなたが保証HWNDをすることができない限り、ワーカースレッドにTWinControlオブジェクトのHWNDを渡すために良いアイデアではありませんスレッドが実行されている間は破棄されません(この例では保証されません)。

この例では、私は、このようなTApplication.Handleウィンドウ(このウィンドウに送信されたメッセージがTApplication.HookMainWindow()を介して処理することができます)、または呼び出しの結果として、スレッドの生活のための永続性が保証された別のHWNDを使用しますAllocateHWnd()。例えば

type 
    TFormMain = class(TForm) 
    procedure FormDestroy(Sender: TObject); 
    ... 
    private 
    CancelCopy: BOOL; // <-- BOOL, not Boolean 
    CopyFileExWnd: HWND; 
    procedure CopyFileExWndProc(var Message: TMessage); 
    ... 
    end; 

... 

type 
    TCopyEx = record 
    Source: String; 
    Dest: String; 
    Handle: HWND; 
    PCancelCopy: PBOOL; 
    end; 
    PCopyEx = ^TCopyEx; 

const 
    CFEX_CANCEL = WM_USER + 1; 

function CopyFileProgress(TotalFileSize, TotalBytesTransferred, StreamSize, 
    StreamBytesTransferred: LARGE_INTEGER; dwStreamNumber, dwCallbackReason: DWORD; 
    hSourceFile, hDestinationFile: THandle; lpData: Pointer): DWORD; stdcall; 
begin 
    ... 
end; 

function CopyExThread(p: PCopyEx): Integer; 
begin 
    try 
    if not CopyFileEx(
     PChar(p.Source), PChar(p.Dest), @CopyFileProgress, p, p.PCancelCopy, COPY_FILE_NO_BUFFERING) then 
    begin 
     if GetLastError() = ERROR_REQUEST_ABORTED then 
     SendMessage(p.Handle, CFEX_CANCEL, 0, 0); 
    end; 
    finally 
    Dispose(p); 
    end; 
    Result := 0; 
end; 

procedure TFormMain.FormDestroy(Sender: TObject); 
begin 
    if CopyFileExWnd <> 0 then 
    DeallocateHWnd(CopyFileExWnd); 
end; 

procedure TFormMain.ButtonCopyClick(Sender: TObject); 
var 
    Params: PCopyEx; 
    ThreadID: Cardinal; 
begin 
    if CopyFileExWnd = 0 then 
    CopyFileExWnd := AllocateHWnd(CopyFileExWndProc); 

    New(Params); 
    Params.Source := EditOriginal.Text; 
    Params.Dest := EditCopied.Text; 
    Params.Handle := CopyFileExWnd; 
    Params.PCancelCopy := @CancelCopy; 

    CancelCopy := FALSE; 
    CloseHandle(BeginThread(nil, 0, @CopyExThread, Params, 0, ThreadID)); 
end; 

procedure TFormMain.ButtonCancelClick(Sender: TObject); 
begin 
    CancelCopy := TRUE; 
end; 

procedure TFormMain.CopyFileExWndProc(var Message: TMessage); 
begin 
    case Message.Msg of 
    CFEX_CANCEL: begin 
     ... 
    end; 
    ... 
    else 
    Message.Result := DefWindowProc(CopyFileExWnd, Message.Msg, Message.WParam, Message.LParam); 
    end; 
end; 
関連する問題