2015-05-23 69 views
2

我寫了一個簡單的組件來監視文件夾,並在檢測到更改時觸發事件。它運作良好......顯然。但我不確定一件事。有時主線程可能需要更新受監控的路徑,我不知道我是否做得對。這是關於SetNewPath程序。這是從主線程執行的,它將從另一個線程中更改UpdatePath變量。當主線程寫入UpdatePath並且組件線程嘗試讀取其Execute週期中的值時,可能會產生衝突?從主線程更改線程的變量值是安全的嗎?

FolderMonitor.pas

unit FolderMonitor; 

interface 

uses 
    SysUtils, Windows, Classes, ExtCtrls; 

type 
    TOnFolderChange = procedure(Sender: TObject) of object; 

    TFolderMonitor = class(TThread) 
    private 
    MainWait: THandle; 
    UpdatePath: Boolean; 
    TimeOut: Cardinal; 
    FPath: String; 
    FOnFolderChange: TOnFolderChange; 
    procedure DoOnFolderChange; 
    procedure SetNewPath(Path:String); 
    protected 
    procedure Execute; override; 
    public 
    constructor Create(const FolderPath: String; OnFolderChangeHandler: TOnFolderChange); 
    destructor Destroy; override; 
    procedure Unblock; 
    property Path: String read FPath write SetNewPath; 
    property OnFolderChange: TOnFolderChange read FOnFolderChange write FOnFolderChange; 
    end; 

implementation 

constructor TFolderMonitor.Create(const FolderPath: String; OnFolderChangeHandler: TOnFolderChange); 
begin 
    inherited Create(True); 
    FOnFolderChange:=OnFolderChangeHandler; 
    FPath:=FolderPath; 
    UpdatePath:=false; 
    FreeOnTerminate:=false; 
    MainWait:=CreateEvent(nil,true,false,nil); 
    Resume; 
end; 

destructor TFolderMonitor.Destroy; 
begin 
    CloseHandle(MainWait); 
    inherited; 
end; 

procedure TFolderMonitor.DoOnFolderChange; 
begin 
    if Assigned(FOnFolderChange) then 
    Synchronize(procedure 
    begin 
    FOnFolderChange(Self); 
    end); 
end; 

procedure TFolderMonitor.Unblock; 
begin 
    PulseEvent(MainWait); 
end; 

procedure TFolderMonitor.SetNewPath(Path:String); 
begin 
    FPath:=Path; 
    UpdatePath:=true; 
    PulseEvent(MainWait); 
end; 

procedure TFolderMonitor.Execute; 
var Filter,WaitResult: Cardinal; 
    WaitHandles: array[0..1] of THandle; 
begin 
    Filter:=FILE_NOTIFY_CHANGE_DIR_NAME + FILE_NOTIFY_CHANGE_FILE_NAME + FILE_NOTIFY_CHANGE_SIZE; 
    WaitHandles[0]:=MainWait; 
    WaitHandles[1]:=FindFirstChangeNotification(PWideChar(FPath),false,Filter); 
    TimeOut:=INFINITE; 

    while not Terminated do begin 
    if UpdatePath then begin 
    if WaitHandles[1]<>INVALID_HANDLE_VALUE then FindCloseChangeNotification(WaitHandles[1]); 
    WaitHandles[1]:=FindFirstChangeNotification(PWideChar(FPath),false,Filter); 
    TimeOut:=INFINITE; 
    UpdatePath:=false; 
    end; 

    if WaitHandles[1] = INVALID_HANDLE_VALUE 
    then WaitResult:=WaitForSingleObject(WaitHandles[0],INFINITE) 
    else WaitResult:=WaitForMultipleObjects(2,@WaitHandles,false,TimeOut); 

    case WaitResult of 
    WAIT_OBJECT_0: Continue; 
    WAIT_OBJECT_0+1: TimeOut:=200; 
    WAIT_TIMEOUT: begin DoOnFolderChange; TimeOut:=INFINITE; end; 
    end; 

    if WaitHandles[1] <> INVALID_HANDLE_VALUE then 
    FindNextChangeNotification(WaitHandles[1]); 
    end; 

    if WaitHandles[1] <> INVALID_HANDLE_VALUE then 
    FindCloseChangeNotification(WaitHandles[1]); 
end; 

end. 

UnitMain.pas

unit UnitMain; 

interface 

uses 
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
    Dialogs, StdCtrls, FolderMonitor; 

type 
    TForm1 = class(TForm) 
    Memo1: TMemo; 
    Edit1: TEdit; 
    Button1: TButton; 
    procedure FormCreate(Sender: TObject); 
    procedure OnFolderChange(Sender: TObject); 
    procedure FormDestroy(Sender: TObject); 
    procedure Button1Click(Sender: TObject); 
    end; 

var 
    Form1: TForm1; 
    Mon: TFolderMonitor; 
    X: integer; 

implementation 

{$R *.dfm} 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
X:=0; 
Mon:=TFolderMonitor.Create('D:\Test',OnFolderChange); 
end; 

procedure TForm1.FormDestroy(Sender: TObject); 
begin 
Mon.Terminate; 
Mon.Unblock; 
Mon.WaitFor; 
Mon.Free; 
end; 

procedure TForm1.OnFolderChange(Sender: TObject); 
begin 
inc(x); 
Memo1.Lines.Add('changed! '+IntToStr(x)); 
end; 

procedure TForm1.Button1Click(Sender: TObject); 
begin 
Mon.Path:=Edit1.Text; 
end; 

end. 
+2

是的,讓一個線程修改字符串字段而另一個線程可以同時讀取它是不安全的, – TLama

+0

如何使其安全? –

回答

0

您有多個線程之間共享的變量,與一個線程修改變量。這種情況被稱爲數據競賽。

一些比賽可能是良性的。這一個不是。如果一個線程修改該變量而另一個線程讀取該變量,則可能會發生錯誤。由於數據類型非常複雜(指向堆分配的字符數組),讀線程很可能試圖從釋放的內存中讀取數據。

對於像這樣的複雜類型,只要您訪問該值,就需要使用互斥鎖。所有的讀寫操作必須由鎖序列化。使用關鍵部分或顯示器。

爲確保您永遠不會執行不受保護的訪問,明智的做法是在代碼中執行此規則。例如,我在這裏描述的TThreadSafe<T>Generic Threadsafe Property

+0

否。代碼塊(或塊)的序列化意味着在任何時候只有一個線程可以被執行。強制所有訪問發生在同一個線程中是實現序列化的一種方式。但是,這是一個非常低效的序列化方法。使用互斥鎖可以讓代碼在不同的線程上執行,並且如果有兩個或多個線程爭用該鎖,則只會有延遲。 –

+0

一般而言,強制執行在特定線程上發生的情況通常僅在該代碼對特定線程有親和力時才執行。經典的例子是與UI線程有親和力的UI代碼。但爲了確保變量的線程安全共享訪問,相互排斥通常是正確的解決方案。對於某些數據類型,可以使用原子操作(稱爲無鎖同步),該操作可以比完整的互斥鎖執行得更好。但是對於像字符串這樣的複雜類型來說這不可行。 –

+0

謝謝你的解釋,我爲你道歉,因爲我刪除了我的評論。我正在閱讀TCriticalSection幫助,並發現所有線程都必須使用關鍵部分來確保安全。 –