2009-07-17 18 views
9

Tôi đang thử nghiệm sự tồn tại của một tệp trong một chia sẻ từ xa (trên máy chủ Windows). Hàm cơ bản được sử dụng để thử nghiệm là của WinAPI GetFileAttributes và điều xảy ra là hàm có thể mất một lượng thời gian (chục giây) trong các tình huống khác nhau, như khi máy chủ đích đang ngoại tuyến, khi có quyền hoặc vấn đề DNS, Tuy nhiên, trong trường hợp cụ thể của tôi, nó luôn là truy cập mạng LAN, vì vậy nếu không thể truy cập tệp trong chưa đầy 1 giây, thì thường không thể truy cập được bằng cách chờ thêm hàng chục giây nữa ..Làm thế nào để tránh các gian hàng mạng trong GetFileAttributes?

Có cách nào khác thay thế cho GetFileAttributes không bị ngăn không? (ngoài việc gọi nó trong một chủ đề và giết chết chủ đề sau một thời gian chờ, mà dường như mang theo túi riêng của nó về các vấn đề)

+0

không thể nghĩ về bất cứ điều gì ngoại trừ mô hình không đồng bộ. –

+0

Trong các trường hợp khác, tôi đã giải quyết vấn đề này bằng cách triển khai một máy chủ web tối giản để phục vụ các tệp được chia sẻ, vì yêu cầu HTTP có thể dễ dàng bị hủy/hết thời gian chờ. Nhưng trong trường hợp này, đây không phải là giải pháp vì nhiều lý do (nhức đầu triển khai, vấn đề bảo mật, v.v.). –

Trả lời

6

Vấn đề không phải là GetFileAttributes thực sự. Nó thường chỉ sử dụng một cuộc gọi đến trình điều khiển hệ thống tệp cơ bản. Đó là IO đang bị trì hoãn.

Tuy nhiên, giải pháp có thể dễ dàng. Gọi CancelSynchronousIo() sau một giây (điều này rõ ràng đòi hỏi một sợi thứ hai là lần đầu tiên của bạn bị mắc kẹt bên trong GetFileAttributes).

+0

Lưu ý rằng CancelSynchronousIo không khả dụng trong Windows XP. –

+0

@MSalters: Tôi bị truy cập bị từ chối (5) lỗi khi sử dụng phương thức GetFileAttributes. Tôi có máy chủ Window 2003 với cấu hình phần cứng thấp. Tôi đã thử các cuộc gọi tương tự với sự cho phép vô hiệu hóa trên các hệ thống khác mà làm việc hoàn hảo. Có thể làm chậm IO gây ra lỗi "truy cập bị từ chối". –

+0

@RahulKP: Khá khó xảy ra. – MSalters

4

Một điều thú vị về đại biểu là bạn luôn có thể BeginInvokeEndInvoke chúng. Chỉ cần chắc chắn rằng phương pháp được gọi là không ném ra một ngoại lệ vì [tôi tin] nó sẽ gây ra một vụ tai nạn (ngoại lệ chưa được giải quyết).

AttributeType attributes = default(AttributeType); 

Action<string> helper = 
    (path) => 
    { 
     try 
     { 
      // GetFileAttributes 
      attributes = result; 
     } 
     catch 
     { 
     } 
    }; 
IAsyncResult asyncResult = helper.BeginInvoke(); 
// whatever 
helper.EndInvoke(); 
// at this point, the attributes local variable has a valid value. 
+1

Vì vậy, về cơ bản, không có hy vọng bên ngoài gói các cuộc gọi API trong một chủ đề? Tôi đã hy vọng cho một giải pháp bên ngoài chủ đề, bởi vì giết chết một chủ đề tại một thời gian chờ không phải là "sạch" (bởi kinh nghiệm, những điều xấu có thể xảy ra), và bỏ qua chủ đề bị trì hoãn có khả năng dẫn đến rất nhiều chủ đề bị ngừng ... –

+0

Xin lỗi, nó cũng có vẻ tôi nhầm tưởng rằng bạn đang làm việc trong NET. (Trả lời một vài trong số những người trước khi điều này). Nếu API không có phiên bản không đồng bộ và/hoặc hết thời gian chờ thì giải pháp luồng có thể là giải pháp đáng tin cậy duy nhất. –

0

Tôi nghĩ giải pháp tốt nhất của bạn là sử dụng một chuỗi chủ đề hồ bơi để thực hiện công việc.

  • gán một đơn vị làm việc để truy vấn các thuộc tính của một tập tin
  • hãy GetFileAttributes chạy để hoàn
  • gửi kết quả trở lại hình thức của bạn
  • khi chức năng thread của bạn hoàn tất, các chủ đề tự động trả lại vào hồ bơi (không cần phải giết nó)

Bằng cách sử dụng nhóm chủ đề bạn tiết kiệm chi phí tạo chuỗi mới.
Và bạn tiết kiệm được nỗi đau khi cố gắng loại bỏ chúng.

Sau đó, bạn có phương pháp helper tiện dụng của bạn chạy thủ tục phương pháp của một đối tượng trên một sợi thread-pool sử dụng QueueUserWorkItem:

RunInThreadPoolThread(
     GetFileAttributesThreadMethod, 
     TGetFileAttributesData.Create('D:\temp\foo.xml', Self.Handle), 
     WT_EXECUTEDEFAULT); 

Bạn tạo các đối tượng để giữ thông tin dữ liệu chủ đề:

TGetFileAttributesData = class(TObject) 
public 
    Filename: string; 
    WndParent: HWND; 
    Attributes: DWORD; 
    constructor Create(Filename: string; WndParent: HWND); 
end; 

và bạn tạo phương thức gọi lại cho chủ đề của mình:

procedure TForm1.GetFileAttributesThreadMethod(Data: Pointer); 
var 
    fi: TGetFileAttributesData; 
begin 
    fi := TObject(Data) as TGetFileAttributesData; 
    if fi = nil then 
     Exit; 

    fi.attributes := GetFileAttributes(PWideChar(fi.Filename)); 

    PostMessage(fi.WndParent, WM_GetFileAttributesComplete, NativeUInt(Data), 0); 
end; 

t hen bạn chỉ xử lý các thông điệp:

procedure WMGetFileAttributesComplete(var Msg: TMessage); message WM_GetFileAttributesComplete; 

procedure TfrmMain.WMGetFileAttributesComplete(var Msg: TMessage); 
var 
    fi: TGetFileAttributesData; 
begin 
    fi := TObject(Pointer(Msg.WParam)) as TGetFileAttributesData; 
    try 
     ShowMessage(Format('Attributes of "%s": %.8x', [fi.Filename, fi.attributes])); 
    finally 
     fi.Free; 
    end; 
end; 

Các huyền diệu RunInThreadPoolThread chỉ là một chút lông tơ mà cho phép bạn thực hiện một phương pháp dụ trong một thread:

Mà chỉ là một wrapper cho phép bạn gọi phương thức trên một Ví dụ biến:

TThreadMethod = procedure (Data: Pointer) of object; 

TThreadPoolCallbackContext = class(TObject) 
public 
    ThreadMethod: TThreadMethod; 
    Context: Pointer; 
end; 

function ThreadPoolCallbackFunction(Parameter: Pointer): Integer; stdcall; 
var 
    tpContext: TThreadPoolCallbackContext; 
begin 
    try 
     tpContext := TObject(Parameter) as TThreadPoolCallbackContext; 
    except 
     Result := -1; 
     Exit; 
    end; 
    try 
     tpContext.ThreadMethod(tpContext.Context); 
    finally 
     try 
      tpContext.Free; 
     except 
     end; 
    end; 
    Result := 0; 
end; 

function RunInThreadPoolThread(const ThreadMethod: TThreadMethod; const Data: Pointer; Flags: ULONG): BOOL; 
var 
    tpContext: TThreadPoolCallbackContext; 
begin 
    { 
     Unless you know differently, the flag you want to use is 0 (WT_EXECUTEDEFAULT). 

     If your callback might run for a while you can pass the WT_ExecuteLongFunction flag. 
       Sure, I'm supposed to pass WT_EXECUTELONGFUNCTION if my function takes a long time, but how long is long? 
       http://blogs.msdn.com/b/oldnewthing/archive/2011/12/09/10245808.aspx 

     WT_EXECUTEDEFAULT (0): 
       By default, the callback function is queued to a non-I/O worker thread. 
       The callback function is queued to a thread that uses I/O completion ports, which means they cannot perform 
       an alertable wait. Therefore, if I/O completes and generates an APC, the APC might wait indefinitely because 
       there is no guarantee that the thread will enter an alertable wait state after the callback completes. 
     WT_EXECUTELONGFUNCTION (0x00000010): 
       The callback function can perform a long wait. This flag helps the system to decide if it should create a new thread. 
     WT_EXECUTEINPERSISTENTTHREAD (0x00000080) 
       The callback function is queued to a thread that never terminates. 
       It does not guarantee that the same thread is used each time. This flag should be used only for short tasks 
       or it could affect other timer operations. 
       This flag must be set if the thread calls functions that use APCs. 
       For more information, see Asynchronous Procedure Calls. 
       Note that currently no worker thread is truly persistent, although worker threads do not terminate if there 
       are any pending I/O requests. 
    } 

    tpContext := TThreadPoolCallbackContext.Create; 
    tpContext.ThreadMethod := ThreadMethod; 
    tpContext.Context := Data; 

    Result := QueueUserWorkItem(ThreadPoolCallbackFunction, tpContext, Flags); 
end; 

tập thể dục cho người đọc: Tạo một hủy cờ bên trong đối tượng GetFileAttributesData mà nói thứ e chủ đề mà phải giải phóng đối tượng dữ liệu và không đăng thông báo cho phụ huynh.


Đó là tất cả một chặng đường dài để nói rằng bạn đang tạo:

DWORD WINAPI GetFileAttributes(
    _In_  LPCTSTR       lpFileName, 
    _Inout_ LPOVERLAPPED     lpOverlapped, 
    _In_  LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine 
); 
+0

Sử dụng các chuỗi gộp không giải quyết được vấn đề mà các luồng có thể bị trì hoãn trong một thời gian rất lâu vì GetFileAttributes đang chờ một số thời gian chờ mạng. Cũng cần bỏ qua các chủ đề không liên quan. Ví dụ nếu bạn truy vấn cùng một tệp hai lần với 10 giây, cuộc gọi thứ nhất có thể bị kẹt trong 30 giây, trong khi lần thứ 2 có thể thành công ngay lập tức (và bạn cần bỏ qua kết quả của cuộc gọi đầu tiên). Ngoài ra, nếu bạn theo dõi nhiều tệp với tần suất vài giây, thì thật dễ dàng để kết thúc với hàng chục hoặc thậm chí hàng trăm chủ đề bị trì hoãn ...không thực tế chút nào:/ –

+0

Bỏ qua các chủ đề không liên quan được giải quyết giống như khi Windows đã cung cấp phiên bản GetFileAttributesEx' chồng lên nhau (tức là không đồng bộ) - bạn phải hủy cuộc gọi hiện tại. Điều đó được giải quyết bằng cách tập thể dục. Mối quan tâm của bạn là phải làm gì khi bạn có hàng chục hoặc hàng trăm chủ đề bị trì hoãn. Tôi sẽ gửi đây không phải là một mối quan tâm, vì hàng đợi của mục công việc người dùng sẽ xếp hàng cho đến khi các mục cũ hơn bị xóa khỏi hàng đợi. Mặc dù, [bất cứ điều gì bạn có thể làm để giúp nó cùng] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa363794.aspx) sẽ trả lại luồng cho hồ bơi nhanh hơn. –

+0

Nó cũng hữu ích để không phải là 'QueueUserWorkItem' sẽ * xếp hàng * các mục của bạn; và không tạo hàng trăm chủ đề. Một trong những mục đích của 'QueueUserWorkItem' là cho phép bạn * xếp hàng * các mục công việc - nhóm chủ đề quyết định khi nào chúng sẽ thực hiện. –

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