更快的DirectoryExists函数?

15

我使用

DirectoryExists (const PathName : String);

检查计算机是否可以访问目录。但是,如果该目录不存在且路径名为网络路径,则需要进行其他步骤。

\\computer1\Data

该方法返回所需时间过长。

肯定有更快的方法可以确定网络文件夹是否不可达。或者我可以配置一些超时参数,让DirectoryExists在内部使用(我查看了源代码,但它只是委派给定义在kernel32中的GetFileAttributes)?

有什么想法吗?


问题是远程计算机上的目录不存在还是您首先无法连接到远程计算机? - Mason Wheeler
可能是两种情况,但大多数情况下我无法连接。 - jpfollenius
1
作为一个反例,我想提到我在家里使用的小型NAS:当我很久没用它时,硬盘会停止旋转。当我第一次访问挂载的目录时,简单的文件列表可能需要大约20秒。所以在那20秒钟内,无法确定某个特定的目录是否存在。甚至在15秒之后,也不能确定它不存在...只有在从主机得到真正的(负面)回应后,才能确定该文件不存在。 - Joachim Sauer
有没有完整的源代码示例,可以作为最终解决方案? - Kiquenet
@Kiquenet:如果您正在寻找“具有完整源代码示例的最终解决方案”,请查看我的答案。 - Michael Hutter
8个回答

19

没有比这更快的方式:

任何访问远程共享的功能都会在该共享不可用时超时。

如果您的超时原因是共享自动断开连接,则以下链接可能会对您有所帮助:

如果应用程序可以在检查完成之前继续运行,则可以将检查放在一个单独的线程中,并在检查完成后更新UI上的状态。

请注意,当您尝试使用多线程方式时,必须证明您的代码没有竞争条件和内存泄漏。超时与异常结合通常会使这成为一项艰巨的任务。


链接不再有效 :-( - Michael Hutter
这个答案似乎是错误的。有一种更快的方法,我在下面的答案中展示。 - Michael Hutter
2
@Michael - 好的,你所做的是将检查放在一个线程中,正如这个答案所说的那样。为什么在主线程中实现超时会证明这个答案是错误的呢?如果有什么问题,你的代码证明了这个答案是正确的。 - Sertac Akyuz
@SertacAkyuz:有三个方面。1)对于“确定网络文件夹不可访问的更快方法”的问题,这个答案说“没有更快的方法”(而我的答案证明了这一点)/ 2)这个答案中的链接无效/ 3)关于单独线程的想法是正确的,但是这个答案没有给出任何源代码来实现这一点。这就是为什么我认为这个答案是错误的原因。 - Michael Hutter
2
@Michael 1) 你的代码只证明了在一段时间后放弃确定目录是否存在是可能的。这是不可靠的,但这是你的设计。对于你来说,时间可能比准确性更重要。2) 一个死链接可能会使链接回答错误,但这里并非如此。FWIW链接可能是这个或者这个。3) 源代码中的“没有更快的方法”?不需要。 - Sertac Akyuz

6

4
如果你可以并行处理某些任务,线程才能起到帮助的作用。如果你的下一个动作依赖于共享资源(例如加载配置文件),在使用线程的情况下,你只能展示一些进度给用户,但并不会加速处理速度。 - Marco van de Voort

5
如果您要测试许多目录,应使用线程并行执行所有查询,因为对于网络共享,通常会有较长的超时时间。

我不需要测试很多目录,只需要测试一个。但是DirectoryExists函数可能需要大约30秒才能返回结果,这很烦人。 - jpfollenius

3
我使用以下代码...
private delegate bool DirectoryExistsDelegate(string folder);

bool DirectoryExistsTimeout(string path, int millisecondsTimeout)
{
    try
    {
        DirectoryExistsDelegate callback = new DirectoryExistsDelegate(Directory.Exists);
        IAsyncResult result = callback.BeginInvoke(path, null, null);

        if (result.AsyncWaitHandle.WaitOne(millisecondsTimeout, false))
        {
            return callback.EndInvoke(result);
        }
        else
        {
            callback.EndInvoke(result);  // Needed to terminate thread?

            return false;
        }
    }

    catch (Exception)
    {
        return false;
    }
}

...这让我能够拥有一个Directory.Exist的超时版本,我会像这样调用它...

bool a = DirectoryExistsTimeout("\\\\machine\\folder", 5000);

这对您的需求来说可以吗?


为了安全/合法,您需要调用 "callback.EndInvoke(result);" 但是调用它会锁定直到异步完成,这违背了代码的初衷。也许这需要在您的代码结尾处完成 - 退出可能是一个好选择。


看起来不错,但这个问题是关于 Delphi 的。你的解决方案似乎是 C#。 - Michael Hutter

3
这是最佳方法。您可以添加一些代码来ping机器以确保它存在,但这仍然会导致许多计算机失败,因为今天许多计算机设置了软件防火墙以忽略ping请求,而且所请求的共享可能不存在。
此外,在某些机器上,如果UNC路径在本地机器上,并且本地机器没有活动的网络卡(例如处于“飞行”模式的Wi-Fi断开的笔记本电脑),则UNC请求也将失败。

3
这个函数对我非常有用:NetDirectoryExists(Path, Timeout)。它使用了线程,并且是TDirectory.Exists(Path)的完美替代品。

Usage:
if NetDirectoryExists('\\computer1\Data', 1000) then ...
if NetDirectoryExists('C:\Folder', 500) then ...

If the Folder exists, the function needs only some milliseconds, same with not existing folders (C:\NotExisting). If it is a not reachable network path (\\ServerNotReady\C$) then it will consume the number of milliseconds given by the second parameter.

Function NetDirectoryExists( const dirname: String; 
                              timeoutMSecs: Dword ): Boolean; 

implementation 


uses 
   Classes, Sysutils, Windows; 


type 
   ExceptionClass = Class Of Exception; 
   TTestResult = (trNoDirectory, trDirectoryExists, trTimeout ); 
   TNetDirThread = class(TThread) 
   private 
     FDirname: String; 
     FErr    : String; 
     FErrclass: ExceptionClass; 
     FResult : Boolean; 
   protected 
     procedure Execute; override; 
   public 
     Function TestForDir( const dirname: String; 
                timeoutMSecs: Dword ):TTestResult; 
   end; 


Function NetDirectoryExists( 
            const dirname: String; timeoutMSecs: Dword ): Boolean; 
 Var 
   res: TTestResult; 
   thread: TNetDirThread; 
 Begin 
   Assert( dirname <> '', 'NetDirectoryExists: dirname cannot be empty.' ); 
   Assert( timeoutMSecs > 0, 'NetDirectoryExists: timeout cannot be 0.' ); 
   thread:= TNetDirThread.Create( true ); 
   try 
     res:= thread.TestForDir( dirname, timeoutMSecs ); 
     Result := res = trDirectoryExists; 
     If res <> trTimeout Then 
       thread.Free; 
     {Note: if the thread timed out it will free itself when it finally 
      terminates on its own. } 
   except 
     thread.free; 
     raise 
   end; 
 End; 


procedure TNetDirThread.Execute; 
 begin 
   try 
     FResult := DirectoryExists( FDirname ); 
   except 
     On E: Exception Do Begin 
       FErr := E.Message; 
       FErrclass := ExceptionClass( E.Classtype ); 
     End; 
   end; 
 end; 


function TNetDirThread.TestForDir(const dirname: String; 
   timeoutMSecs: Dword): TTestResult; 
 begin 
   FDirname := dirname; 
   Resume; 
   If WaitForSingleObject( Handle, timeoutMSecs ) = WAIT_TIMEOUT 
   Then Begin 
     Result := trTimeout; 
     FreeOnTerminate := true; 
   End 
   Else Begin 
     If Assigned( FErrclass ) Then 
       raise FErrClass.Create( FErr ); 
     If FResult Then 
       Result := trDirectoryExists 
     Else 
       Result := trNoDirectory; 
   End; 
 end; 


4
如果你知道并且公开后果的话,这可能是一个可行的方法。如果你使用NetDirectoryExists函数,它会比操作系统默认值返回更多的错误结果。假设你设置了1000毫秒的超时时间,并且目录仅需3秒钟就可以到达,那该怎么办?或者需要6秒钟,甚至需要一分钟。值得一提的是,我认为在单个环境中进行少量测试,然后认为自己能够得出比协议和系统开发人员经验更丰富的更好的超时值是相当乐观的。 - Sertac Akyuz

2

在类似于你所描述的情况下,我首先向服务器添加了一个ICMP ping。如果服务器没有响应ping,则我认为它已经宕机。您可以自行决定ping的超时时间,因此可以将其设置得比尝试打开文件共享时内部使用的超时时间短得多。


1
类似于这样的内容,现代Windows在IcmpSendEcho方面有一些特殊之处,因此它仍然可以在没有提升权限的情况下工作。 - Stijn Sanders

0
如果两台计算机在同一个域中,处理共享文件时会加快文件操作速度。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接