2017-05-26 180 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

從技術上講,您所顯示的代碼很好,並且可以按預期工作。

但是,它有一個小錯誤。您正在將錯誤的指針值傳遞給參數CopyFileEx()pbCancel。但是,您的代碼不會崩潰,因爲您傳遞的指針實際上被設置爲nil,並且pbCancel將接受nil指針,因此CopyFileEx()將忽略該參數。

你是什麼應該做的是通過一個BOOL變量,你可以在任何時間爲TRUE取消複製的地址。 CopyFileEx()將爲您監控該變量,當變量設置時,您不需要手動返回PROGRESS_CANCEL回調函數(如果回調函數遇到與複製本身無關的錯誤,則返回PROGRESS_CANCEL,並且您想中止該複製作爲結果的錯誤)。不過,我不會使用全局變量。我將使用對執行副本的表單本地的變量。

嘗試一些更喜歡這個:

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.Handle屬性傳遞HWND到線程。如果TForm在線程仍在運行時出於任何原因破壞/重新創建其HWND,則TCopyEx.Handle值將指向無效窗口(或更糟糕的是,指向重新使用舊窗口的新窗口HWND值)。

一般來說,TWinControl.Handle屬性不是線程安全的,所以單是這一理由,它不是一個TWinControl對象的HWND傳遞給工作線程是一個好主意,除非你能保證HWND在線程運行時不會被銷燬(在這個例子中,這是不能保證的)。

在本例中,我會用這是保證一個不同HWND成爲螺紋的壽命,如TApplication.Handle窗口(發送到這個窗口的消息可以經由TApplication.HookMainWindow()處理),或調用的結果持久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;