40

I have to check if a set of file paths represent an existing file.

It works fine except when the path contains a network share on a machine that's not on the current network. In this case it takes a pretty long time (30 or 60 seconds) to timeout.

Questions

  • Is there a way to shorten the timeout for non existing network shares? (I'm certain that when they do exist they'll answer quickly, so a timeout of 1 sec would be fine)

  • Is there any other way to solve this issue without starting to cache and making the algorithm more complex? (ie, I already know these X network shares don't exist, skip the rest of the matching paths)

UPDATE: Using Threads work, not particularly elegant, though

public bool pathExists(string path) 
{
    bool exists = true;
    Thread t = new Thread
    (
        new ThreadStart(delegate () 
        {
            exists = System.IO.File.Exists(path); 
        })
    );
    t.Start();
    bool completed = t.Join(500); //half a sec of timeout
    if (!completed) { exists = false; t.Abort(); }
    return exists;
}

This solution avoids the need for a thread per attempt, first check which drives are reachable and store that somewhere.


Experts exchange solution:

First of all, there is a "timeout" value that you can set in the IsDriveReady function. I have it set for 5 seconds, but set it for whatever works for you.

3 methods are used below:

  1. The first is the WNetGetConnection API function that gets the UNC (\servername\share) of the drive
  2. The second is our main method: The Button1_Click event
  3. The third is the IsDriveReady function that pings the server.

This worked great for me! Here you go:

'This API Function will be used to get the UNC of the drive
Private Declare Function WNetGetConnection Lib "mpr.dll" Alias _
"WNetGetConnectionA" _
(ByVal lpszLocalName As String, _
ByVal lpszRemoteName As String, _
ByRef cbRemoteName As Int32) As Int32


'This is just a button click event - add code to your appropriate event
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim bIsReady As Boolean = False

    For Each dri As IO.DriveInfo In IO.DriveInfo.GetDrives()

        'If the drive is a Network drive only, then ping it to see if it's ready.
        If dri.DriveType = IO.DriveType.Network Then

            'Get the UNC (\\servername\share) for the 
            '    drive letter returned by dri.Name
            Dim UNC As String = Space(100)
            WNetGetConnection(dri.Name.Substring(0, 2), UNC, 100)

            'Presuming the drive is mapped \\servername\share
            '    Parse the servername out of the UNC
            Dim server As String = _
                 UNC.Trim().Substring(2, UNC.Trim().IndexOf("\", 2) - 2)

            'Ping the server to see if it is available
            bIsReady = IsDriveReady(server)

        Else
            bIsReady = dri.IsReady

        End If

        'Only process drives that are ready
        If bIsReady = True Then
            'Process your drive...
            MsgBox(dri.Name & " is ready:  " & bIsReady)

        End If

    Next

    MsgBox("All drives processed")

End Sub

Private Function IsDriveReady(ByVal serverName As String) As Boolean
    Dim bReturnStatus As Boolean = False

    '***  SET YOUR TIMEOUT HERE  ***
    Dim timeout As Integer = 5    '5 seconds

    Dim pingSender As New System.Net.NetworkInformation.Ping()
    Dim options As New System.Net.NetworkInformation.PingOptions()

    options.DontFragment = True

    'Enter a valid ip address
    Dim ipAddressOrHostName As String = serverName
    Dim data As String = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    Dim buffer As Byte() = System.Text.Encoding.ASCII.GetBytes(data)
    Dim reply As System.Net.NetworkInformation.PingReply = _
                pingSender.Send(ipAddressOrHostName, timeout, buffer, options)

    If reply.Status = Net.NetworkInformation.IPStatus.Success Then
        bReturnStatus = True

    End If

    Return bReturnStatus
End Function
Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Vinko Vrsalovic
  • 330,807
  • 53
  • 334
  • 373
  • see http://stackoverflow.com/questions/726602/how-to-prevent-timeout-when-inspecting-unavailable-network-share-c – mangokun Aug 05 '09 at 13:07
  • Yes, I've already voted to close this as a dupe. Chris Pebble deleted his answer where he linked to the question (I don't know why) – Vinko Vrsalovic Aug 05 '09 at 13:18
  • The Expert Exchange solution doesn't seem very good. The file server may very well not respond to ping. That doesn't mean it's not available. – mhenry1384 Oct 14 '10 at 16:56
  • @mhenry1384 That's true. But it's useful if you know it should respond to pings. If you want to support the ping being blocked, you could try something like connecting to SMB port and seeing if it answers... – Vinko Vrsalovic Oct 14 '10 at 19:41
  • I'd rather the thread method, its more controlable. – AbiusX Sep 08 '11 at 05:42
  • 1
    @Kiquenet The threading solution is what I tend to use these days. It's pretty efficient. – Vinko Vrsalovic May 16 '13 at 09:56

7 Answers7

8

In a nutshell

  1. Build a list of available drives.
  2. Try to resolve the driveletter to an UNC name.
  3. Try to ping the drive.

Edit regarding Bill's comment

if Google is not the referer, EE doesn't show the answer for free. Links to EE are not helpful.

OP found the article I've mentioned in my original answer and was kind enough to include the source code for the solution to his question.

Community
  • 1
  • 1
Lieven Keersmaekers
  • 57,207
  • 13
  • 112
  • 146
7

Another "thread solution":

/// <sumary>Check if file exists with timeout</sumary>
/// <param name="fileInfo">source</param>
/// <param name="millisecondsTimeout">The number of milliseconds to wait,
///  or <see cref="System.Threading.Timeout.Infinite"/> (-1) to wait indefinitely.</param>
/// <returns>Gets a value indicating whether a file exists.</returns>
public static bool Exists(this FileInfo fileInfo, int millisecondsTimeout)
{
    var task = new Task<bool>(() => fileInfo.Exists);
    task.Start();
    return task.Wait(millisecondsTimeout) && task.Result;
}

Source: http://www.jonathanantoine.com/2011/08/18/faster-file-exists/

There are some concerns about "drive not responding fast enough", so this is compromise between speed and "the truth". Don't you use It if you want to be sure 100%.

Jan 'splite' K.
  • 1,667
  • 2
  • 27
  • 34
4

Use Threads to do the checks. I think that threads can be timed out.

Nick
  • 3,217
  • 5
  • 30
  • 42
1

This worked GREAT for me!
Here's IsDriveReady() in C#:

using System.Net;
private bool IsDriveReady(string serverName)
{
   // ***  SET YOUR TIMEOUT HERE  ***     
   int timeout = 5;    // 5 seconds 
   System.Net.NetworkInformation.Ping pingSender = new System.Net.NetworkInformation.Ping();
   System.Net.NetworkInformation.PingOptions options = new System.Net.NetworkInformation.PingOptions();
   options.DontFragment = true;      
   // Enter a valid ip address     
   string ipAddressOrHostName = serverName;
   string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
   byte[] buffer = System.Text.Encoding.ASCII.GetBytes(data);
   System.Net.NetworkInformation.PingReply reply = pingSender.Send(ipAddressOrHostName, timeout, buffer, options);
   return (reply.Status == System.Net.NetworkInformation.IPStatus.Success);
}
dlchambers
  • 3,511
  • 3
  • 29
  • 34
  • 1
    true story: ping was turned off on some enterprise cloud file storage by IT department. Yeah. It broke lots of things but they refuse to turn it on becouse "reasons". – Jan 'splite' K. Dec 05 '19 at 08:21
0

I found the pathExists with thread timeout function useful but finally realized that it needed a change from File.Exists to Directory.Exists to work correctly.

0

Also another solution is to use CancellationToken

    public static async Task<bool> FileExistsWithTimeout(string path, int timeoutMilliseconds)
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        CancellationToken token = cts.Token;

        Task<bool> directoryExistsTask = Task.Run(() => Path.Exists(path), token);

        if (await Task.WhenAny(directoryExistsTask, Task.Delay(timeoutMilliseconds, token)) == directoryExistsTask)
        {
            cts.Cancel();
            return await directoryExistsTask;
        }
        else
        {
            return false;
        }
    }
Polyvios P
  • 442
  • 4
  • 11
-1

Couldn't you just use FileMonitor control for this so that an event fires when it gets removed? Then you can set bool to false;

jay_t55
  • 11,362
  • 28
  • 103
  • 174