Windows 如何避免GetFileAttributes中的网络暂停?

Windows 如何避免GetFileAttributes中的网络暂停?,windows,networking,Windows,Networking,我正在测试远程共享(在Windows服务器上)中是否存在文件。用于测试的底层函数是WinAPI的GetFileAttributes,发生的情况是,该函数在各种情况下可能会花费过多的时间(几十秒),例如目标服务器脱机、出现权限或DNS问题等 然而,在我的特殊情况下,它始终是一个局域网访问,因此,如果文件在不到1秒的时间内无法访问,那么通常需要等待几十秒才能访问它 有没有一种替代GetFileAttributes的方法不会暂停?(除了在线程中调用它并在超时后终止线程之外,这似乎带来了它自己的问题)关

我正在测试远程共享(在Windows服务器上)中是否存在文件。用于测试的底层函数是WinAPI的GetFileAttributes,发生的情况是,该函数在各种情况下可能会花费过多的时间(几十秒),例如目标服务器脱机、出现权限或DNS问题等

然而,在我的特殊情况下,它始终是一个局域网访问,因此,如果文件在不到1秒的时间内无法访问,那么通常需要等待几十秒才能访问它


有没有一种替代GetFileAttributes的方法不会暂停?(除了在线程中调用它并在超时后终止线程之外,这似乎带来了它自己的问题)

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

AttributeType attributes=default(AttributeType);
动作助手=
(路径)=>
{
尝试
{
//GetFileAttributes
属性=结果;
}
抓住
{
}
};
IAsyncResult asyncResult=helper.BeginInvoke();
//随便
EndInvoke();
//此时,attributes局部变量具有有效值。

问题其实不是GetFileAttributes。它通常只使用一个对底层文件系统驱动程序的调用。是那个木卫一在拖延


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

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

  • 指定工作单元以查询文件的属性
  • GetFileAttributes
    运行完成
  • 将结果发布回您的表单
  • 当线程函数完成时,线程自动返回池(无需终止)
通过使用线程池,您可以节省创建新线程的成本。
你省去了摆脱他们的痛苦

然后您就有了方便的helper方法,它使用
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
对象内创建一个Cancelled标志,告诉线程它必须释放数据对象,而不是向父对象发布消息


要说你在创造,还有很长的路要走:

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

除了异步模型之外,我想不出任何东西。在其他情况下,我通过部署一个最低限度的web服务器来服务共享文件,解决了这个问题,因为HTTP请求很容易被取消/超时。但在这种情况下,由于各种原因(部署问题、安全问题等),这并不是解决方案。因此,基本上,除了在线程中包装API调用之外,没有希望了?我希望在线程之外找到一个解决方案,因为在超时时杀死一个线程并不是“干净的”(根据经验,坏事情可能会发生),忽略被暂停的线程可能会导致大量被暂停的线程……对不起,我似乎也错误地认为您在.NET中工作(在这之前回答了一些问题)。如果API没有可用的异步和/或超时版本,则线程解决方案可能是唯一可靠的解决方案。请注意,CancelSynchronousIo在Windows XP中不可用。@MSalters:I Get access is denied(5)使用GetFileAttributes方法时出错。我的Windows2003服务器硬件配置较低。我尝试了同样的呼叫,但在其他系统上禁用了权限,这些系统工作得很好。减慢IO速度可能会导致“访问被拒绝”错误。@RahulKP:不太可能。FWIW从多媒体计时器(timeSetEvent)调用CancelSynchronousIo()似乎可以正常工作,因此代码只需设置计时器,调用GetFileAttributes,然后在“finally”中取消计时器使用池线程并不能解决当GetFileAttributes等待某些网络超时时线程可能会暂停很长时间的问题。还需要忽略不相关的线程。例如,如果在10秒内查询同一个文件两次,第一次调用可能会停滞30秒,而第二次调用可能会立即成功(并且您需要忽略第一次调用的结果)。此外,如果您以几秒钟的频率监视多个文件,那么很容易会导致数十个甚至数百个暂停的线程。。。根本不实用:/n忽略无关线程的解决方法与Windows本机已提供重叠(即异步)
GetFileAttributesEx
版本的解决方法相同-您必须取消现有调用。这个问题通过练习解决了。您关心的是当您有几十个或数百个停止的线程时该怎么办。我认为这不是一个问题,因为用户工作项队列将对项目进行排队,直到从队列中刷新旧项目。虽然,将更快地将线程返回池。不要让
QueueUserWorkItem
将您的项目排队,这也很有用;而不是创建数百个线程。
QueueUserWorkItem
的目的之一是让您对工作项进行排队-线程池决定它们何时执行。如果队列大小有限,则暂停的调用将阻止(或延迟)其他调用(否则可能会很快),因此这会造成瓶颈。调用CancelSynchronousIo()i
DWORD WINAPI GetFileAttributes(
  _In_      LPCTSTR                         lpFileName,
  _Inout_   LPOVERLAPPED                    lpOverlapped,
  _In_      LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);