如何避免GetFileAttributes中的网络停顿?

时间:2009-07-17 08:46:11

标签: windows networking

我正在测试远程共享(在Windows服务器上)中是否存在文件。用于测试的基础函数是WinAPI的 GetFileAttributes ,会发生的情况是,在各种情况下,该函数可能需要过多的时间(几十秒),例如当目标服务器处于脱机状态时,权利或DNS问题等。

但是,在我的特定情况下,它总是一个局域网访问,所以如果文件在不到1秒的时间内无法访问,那么通常等待几十秒就无法访问...

GetFileAttributes有替代方案吗? (除了在线程中调用它并在超时后杀死线程,这似乎带来了自己的问题)

3 个答案:

答案 0 :(得分:7)

问题不在于GetFileAttributes。它通常只使用一次对底层文件系统驱动程序的调用。这就是IO停滞不前。

不过,解决方案可能很简单。一秒后调用CancelSynchronousIo()(这显然需要第二个线程,因为第一个线程被卡在GetFileAttributes中)。

答案 1 :(得分:4)

关于代表的一个很酷的事情是,您可以始终BeginInvokeEndInvoke。只要确保被调用的方法不会抛出异常,因为[我相信]它会导致崩溃(未处理的异常)。

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.

答案 2 :(得分:0)

我认为您最好的解决方案是使用线程池线程来执行工作。

  • 指定一个工作单元来查询文件的属性
  • GetFileAttributes运行完成
  • 将结果发布回您的表单
  • 当你的线程函数完成时,线程会自动返回池(不需要杀死它)

通过使用线程池,您可以节省创建新线程的成本 而且你省去了试图摆脱它们的痛苦。

然后你有了一个方便的帮助器方法,它使用QueueUserWorkItem在线程池线程上运行一个对象的方法过程:

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

您创建了保存线程数据信息的对象:

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

并创建线程回调方法:

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;

然后您只需处理消息:

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;

神奇的RunInThreadPoolThread只是让你在线程中执行实例方法的一点点:

这只是一个允许你在实例变量上调用方法的包装器:

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;

为读者练习:在GetFileAttributesData对象中创建一个已取消的标志,该标志告诉线程 it 必须释放数据对象和将消息发布到父级。

说你创造了很长的路要走:

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