9

Tôi sử dụng ReadDirectoryChangesW để xem thư mục được chỉ định và cập nhật cấu trúc lập chỉ mục bất cứ khi nào có thay đổi được phát hiện. Tôi sử dụng đoạn mã sau (khoảng)Tại sao ReadDirectoryChangesW bỏ qua các sự kiện?

var 
    InfoPointer : PFileNotifyInformation; 
    NextOffset : DWORD; 
... 
while (not Terminated) do begin 
    if ReadDirectoryChangesW (FDirHandle, FBuffer, FBufferLength, True, 
          FFilter, @BytesRead, @FOverlap, nil) then 
    begin 
    WaitResult := WaitForMultipleObjects (2, @FEventArray, False, INFINITE); 
    if (WaitResult = waitFileChange) then 
     begin 
     InfoPointer := FBuffer; 
     repeat 
     NextOffset := InfoPointer.NextEntryOffset; 
     ... 
     PByte (InfoPointer) := PByte (InfoPointer) + NextOffset; 
     until NextOffset = 0; 
     end; 
    end; 
end; 

Filter là

FFilter := FILE_NOTIFY_CHANGE_FILE_NAME or 
      FILE_NOTIFY_CHANGE_DIR_NAME or 
      FILE_NOTIFY_CHANGE_SIZE or 
      FILE_NOTIFY_CHANGE_LAST_WRITE; 

và xử lý thư mục thu được như thế này:

FDirHandle := CreateFile (PChar (FDirectoryWatch.WatchedDirectory), 
          FILE_LIST_DIRECTORY or GENERIC_READ, 
          FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, 
          nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS or 
          FILE_FLAG_OVERLAPPED, 0);   

Khi tôi xóa nhiều file tôi nhận được chỉ là một sự kiện và NextOffset là 0! Và khi tôi xóa một thư mục, tôi chỉ nhận được một sự kiện cho thư mục. Nếu tôi muốn một sự kiện cho mỗi tệp trong thư mục thì sao?

Mọi trợ giúp sẽ được đánh giá cao.

Trả lời

15

Dường như với tôi rằng bạn đang trộn các cách khác nhau để sử dụng ReadDirectoryChangesW(), bạn cả hai xác định CỜ FILE _ _ chồng chéo cờ khi mở thư mục và cung cấp một con trỏ đến lpOverlapped tham số , nghĩa là bạn muốn đợi sự kiện trong cấu trúc và xử lý I/O không đồng bộ; và cùng lúc bạn gọi ReadDirectoryChangesW() trong một vòng lặp trong chuỗi công nhân. Trước tiên, tôi sẽ thử lại với lpOverlapped được đặt thành nil, vì bạn có một chuỗi chuyên dụng và có thể sử dụng chế độ đồng bộ.

Trong tài liệu của hàm ReadDirectoryChangesW() API, các cách khác nhau để sử dụng nó được mô tả. Lưu ý rằng nó cũng có thể là tràn bộ đệm, do đó, thay đổi các sự kiện có thể bị mất anyway. Có lẽ bạn nên suy nghĩ lại chiến lược của bạn chỉ dựa vào chức năng này, so sánh ảnh chụp nhanh của nội dung thư mục cũng có thể hoạt động.

Edit:

đang thay đổi nội dung của bạn trông đẹp hơn. Trong các thử nghiệm của tôi, tuy nhiên ReadDirectoryChangesW() đã hoạt động như được quảng cáo, có một vài mục nhập dữ liệu trong bộ đệm được trả về hoặc có nhiều bộ đệm để xử lý. Điều này phụ thuộc vào thời gian, sau khi nhấn một breakpoint trong Delphi tôi nhận được một số mục trong một bộ đệm.

Đối với đầy đủ tôi đính kèm mã kiểm tra, thực hiện sử dụng Delphi 5:

type 
    TWatcherThread = class(TThread) 
    private 
    fChangeHandle: THandle; 
    fDirHandle: THandle; 
    fShutdownHandle: THandle; 
    protected 
    procedure Execute; override; 
    public 
    constructor Create(ADirectoryToWatch: string); 
    destructor Destroy; override; 

    procedure Shutdown; 
    end; 

constructor TWatcherThread.Create(ADirectoryToWatch: string); 
const 
    FILE_LIST_DIRECTORY = 1; 
begin 
    inherited Create(TRUE); 
    fChangeHandle := CreateEvent(nil, FALSE, FALSE, nil); 
    fDirHandle := CreateFile(PChar(ADirectoryToWatch), 
    FILE_LIST_DIRECTORY or GENERIC_READ, 
    FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, 
    nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED, 0); 
    fShutdownHandle := CreateEvent(nil, FALSE, FALSE, nil); 
    Resume; 
end; 

destructor TWatcherThread.Destroy; 
begin 
    if fDirHandle <> INVALID_HANDLE_VALUE then 
    CloseHandle(fDirHandle); 
    if fChangeHandle <> 0 then 
    CloseHandle(fChangeHandle); 
    if fShutdownHandle <> 0 then 
    CloseHandle(fShutdownHandle); 
    inherited Destroy; 
end; 

procedure TWatcherThread.Execute; 
type 
    PFileNotifyInformation = ^TFileNotifyInformation; 
    TFileNotifyInformation = record 
    NextEntryOffset: DWORD; 
    Action: DWORD; 
    FileNameLength: DWORD; 
    FileName: WideChar; 
    end; 
const 
    BufferLength = 65536; 
var 
    Filter, BytesRead: DWORD; 
    InfoPointer: PFileNotifyInformation; 
    Offset, NextOffset: DWORD; 
    Buffer: array[0..BufferLength - 1] of byte; 
    Overlap: TOverlapped; 
    Events: array[0..1] of THandle; 
    WaitResult: DWORD; 
    FileName, s: string; 
begin 
    if fDirHandle <> INVALID_HANDLE_VALUE then begin 
    Filter := FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_DIR_NAME 
     or FILE_NOTIFY_CHANGE_SIZE or FILE_NOTIFY_CHANGE_LAST_WRITE; 

    FillChar(Overlap, SizeOf(TOverlapped), 0); 
    Overlap.hEvent := fChangeHandle; 

    Events[0] := fChangeHandle; 
    Events[1] := fShutdownHandle; 

    while not Terminated do begin 
     if ReadDirectoryChangesW (fDirHandle, @Buffer[0], BufferLength, TRUE, 
     Filter, @BytesRead, @Overlap, nil) 
     then begin 
     WaitResult := WaitForMultipleObjects(2, @Events[0], FALSE, INFINITE); 
     if WaitResult = WAIT_OBJECT_0 then begin 
      InfoPointer := @Buffer[0]; 
      Offset := 0; 
      repeat 
      NextOffset := InfoPointer.NextEntryOffset; 
      FileName := WideCharLenToString(@InfoPointer.FileName, 
       InfoPointer.FileNameLength); 
      SetLength(FileName, StrLen(PChar(FileName))); 
      s := Format('[%d] Action: %.8xh, File: "%s"', 
       [Offset, InfoPointer.Action, FileName]); 
      OutputDebugString(PChar(s)); 
      PByte(InfoPointer) := PByte(DWORD(InfoPointer) + NextOffset); 
      Offset := Offset + NextOffset; 
      until NextOffset = 0; 
     end; 
     end; 
    end; 
    end; 
end; 

procedure TWatcherThread.Shutdown; 
begin 
    Terminate; 
    if fShutdownHandle <> 0 then 
    SetEvent(fShutdownHandle); 
end; 

//////////////////////////////////////////////////////////////////////////////// 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    fThread := TWatcherThread.Create('D:\Temp'); 
end; 

procedure TForm1.FormDestroy(Sender: TObject); 
begin 
    if fThread <> nil then begin 
    TWatcherThread(fThread).Shutdown; 
    fThread.Free; 
    end; 
end; 

Xóa một thư mục nào thực sự chỉ trả lại một sự thay đổi cho nó, không có gì cho các tập tin chứa trong nó. Nhưng nó có ý nghĩa, khi bạn đang xem xử lý của thư mục mẹ chỉ. Nếu bạn cần thông báo cho các thư mục con, bạn có thể cũng cần phải xem chúng.

+0

Xin lỗi vì bình luận bị trì hoãn của tôi.Tôi đã sử dụng phiên bản đồng bộ trước đó (với cùng một vấn đề chính xác) nhưng đã chuyển sang phiên bản không đồng bộ vì tôi không tìm thấy cách để xóa hoàn toàn luồng. Mặc dù vậy, tôi đã bỏ lỡ một dòng quan trọng trong mã ví dụ (lệnh gọi chặn đến WaitForMultipleObjects, có thể bị hạn chế bởi một sự kiện thay đổi tập tin hoặc bởi một sự kiện kết thúc). Tôi chỉnh sửa câu hỏi cho phù hợp. (...) – jpfollenius

+0

Ý của bạn là gì? Nếu bạn muốn lặp lại tất cả các tệp bằng FindFirst, FindNext: Trước đây tôi đã sử dụng cách tiếp cận này nhưng tôi muốn tránh (1) thời gian phát hiện thay đổi bị trì hoãn khi sử dụng các thư mục lớn và (2) tải I/O liên tục cho chuỗi chỉ mục làm chậm tất cả các hoạt động I/O khác. – jpfollenius

+1

Đồng ý với bình luận thứ hai của bạn, nhưng như các tài liệu MSDN nói rằng bạn cần phải được chuẩn bị cho tràn bộ đệm bên trong, và trong trường hợp đó việc quét toàn bộ (lại) thư mục là cần thiết. – mghie

4

Chúng tôi đã gặp sự cố tương tự với việc mất các sự kiện, đặc biệt nếu có nhiều thay đổi xảy ra cùng một lúc, tức là. 500 tệp được sao chép vào thư mục được giám sát.

Cuối cùng, chúng tôi đã tìm thấy Cromis và sử dụng Directory watch. Chúng tôi chưa bao giờ nhìn lại.

+0

thư mục đồng hồ là tốt thực sự. cho khả năng tương thích 64 bit, bạn cần thay thế getWindowLong bằng getWindowLongPtr (cũng cho thiết lập ...) – Ampere

Các vấn đề liên quan