2015-05-23 5 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-код, который имеет сходство с потоком пользовательского интерфейса. Но для обеспечения поточного совместного доступа к переменной, взаимное исключение, как правило, является правильным решением. Для определенных типов данных доступны атомарные операции (известные как синхронизация без блокировки), которые могут работать лучше, чем полная блокировка взаимного исключения. Но это небезопасно для сложного типа, такого как строка. –

+0

Спасибо за объяснения, и я извиняюсь за вас, потому что я удалил свой комментарий. Я читал справку TCriticalSection и обнаружил, что все потоки должны использовать критические разделы для обеспечения безопасности. –

 Смежные вопросы

  • Нет связанных вопросов^_^