25

I wish there was a File.ExistsAsync()

I have:

bool exists = await Task.Run(() => File.Exists(fileName));

Using a thread for this feels like an antipattern. Is there a cleaner way?

Johan Larsson
  • 17,112
  • 9
  • 74
  • 88
  • Can I ask why a thread is required at all? – Simon Whitehead Sep 29 '13 at 09:55
  • 2
    Hmm, I don't want a thread but I don't want to block either. – Johan Larsson Sep 29 '13 at 09:56
  • Does File.Exists actually block? – dtb Sep 29 '13 at 09:57
  • Not sure to be honest but I can't see how it does not. – Johan Larsson Sep 29 '13 at 09:58
  • 6
    Using `File.Exists()` is an automatic race condition. It could return `true`, meanwhile another thread has just deleted it. Just saying. – Matthew Watson Sep 29 '13 at 10:05
  • A `File.ExistsAsync` method would have to spin up a new thread internally, so I'm not sure why it only feels like an antipattern when you're implementing it in your own code. Is there any situation you're trying to avoid by wanting to avoid blocking? File.Exists checks on a local drive should not block for any meaningful period of time. – pixelbadger Sep 29 '13 at 10:17
  • `Task.Run()` does not 'spin up a Thread', it just uses one from the pool. – H H Sep 29 '13 at 10:25
  • true, sloppy writing by me, edited. – Johan Larsson Sep 29 '13 at 10:34
  • I realize now how dumb this question was, ty sirs :D – Johan Larsson Sep 29 '13 at 11:11
  • 8
    @MatthewWatson The synchronous version also introduces a race condition; another thread (or, more likely, another process) could have just deleted it. Still, `File.Exists` could take minutes to complete (for instance, a machine could not exist, a connection could be slow, or a disk could not be read), so a `File.ExistsAsync` using a I/O completion port would be most welcome. Unfortunately, it seems the internal `GetFileAttributesEx` call cannot be overlapped, so there wouldn't be much of a point. – ErikHeemskerk Nov 25 '13 at 12:40

3 Answers3

4

There is no cleaner way than your solution.

The problems of race conditions aside I believe your solution can be used in some situations. e.g.

I have static file content in many different folders. (in my case cshtml views,script files, css files, for mvc) These files (which do not change much, during application execution) are always checked for in every request to the webserver, due to my application architecture, there are alot more places that files are checked for than in the default mvc application. So much so that file.exists takes up quite a portion of time each request.

so race conditions will generally not happen. The only interesting question for me is performance

starting a task with Task.Factory.StartNew() takes 0.002 ms (source Why so much difference in performance between Thread and Task?)

calling file.exists takes "0.006255ms when the file exists and 0.010925ms when the file does not exist." [Richard Harrison]

so by simple math calling the async File.Exists takes 0.008 ms up to 0.012 ms

in the best case async File.Exists takes 1.2 times as long as File.Exists and in the worst case it takes 1.3 times as long. (in my case most paths that are searched do not exist) so most of the time a File.Exists is mostly close to 0.01 ms

so it is not that much overhead, and you can utilize multiple cores/ harddisk controllers etc. more efficiently. With these calculations you can see that asynchroniously checking for existence of 2 files you will already have a performance increase of 1.6 in the worst case (0.02/ 0.012 )

well i'm just asyning async File.Exists is worth it in specific situations.

caveats of my post: i might have not calculated everything correctly i rounded alot i did not measure performance on a single pc i took performance from other posts i just added the time of File.Exists and Task.Factory.StartNew() (this may be wrong) i disregard alot of sideffects of multithreading

Community
  • 1
  • 1
toeb
  • 481
  • 5
  • 9
4

Long time since this thread, but I found it today...

ExistsAsync should definitely be a thing. In fact, in UWP, you have to use Async methods to find out if a file exists, as it could take longer than 50ms (anything that 'could' take longer than 50ms should be async in UWP language).

However, this is not UWP. The reason I need it is to check for folder.exists which if on a network share, remote disk, or idle disk would block the UI. So I can put all the messages like "checking..." but the UI wouldn't update without aysnc (or ViewModel, or timers, etc.)

Your code:

bool exists = await Task.Run(() => File.Exists(fileName));

works perfectly. In my code, I have both (Exists and ExistsAsync) so that I can run Exists() when running on a non UI thread and don't have to worry about the overhead.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
  • I was getting error500 (write error) for TOO many requests on my C# aspnetcore webapi (image upload), After I put the upload file check inside a extra thread (like explained here), I got no more error500 :) – Arthur Melo Mar 17 '21 at 19:47
-13

There isn't a File.ExistsAsync probably for good reason; because it makes absolutely no sense to have one; File.Exists is not going to take very long; I tested it as 0.006255ms when the file exists and 0.010925ms when the file does not exist.

There are a few times when it is sensible to call File.Exists; however usually I think the correct solution would be to open the file (thus preventing deletion), catching any exceptions - as there is no guarantee that the file will continue to exist after the call to File.Exists.

When you want to create a new file and not overwrite the old one :

File.Open("fn", FileMode.CreateNew)

For most of the use cases I can think of File.Open() (whether for existing or create new) is going to be better because once the call succeeds you will have a handle to the file and be able to do something with it. Even when using the file's existence as a flag I think I'd still open and close it. The only time I've really used File.Exists is when checking to see if a local HTML file is there before calling the browser so I can show a nice error message when it isn't.

The is no guarantee that something else won't delete the file after File.Exists; so if you did open it after checking with File.Exists the open call could still fail.

In my tests using a FileExists on network drive takes longer than File.Open, File.Exists takes 1.5967ms whereas File.OpenRead takes 0.3927ms)

Maybe if you could expand upon why you're doing this we'd be better able to answer; until then I'd say that you shouldn't do this

Richard Harrison
  • 19,247
  • 4
  • 40
  • 67
  • I agree that ExistsAsync would not make much sense, checking while using threads is dumb. Gonna do `try-catch`instead on read instead. – Johan Larsson Sep 29 '13 at 11:10
  • 76
    What if the file is on a network drive? Then even something as simple as finding whether a file exists could take a long time. – svick Sep 29 '13 at 12:56
  • have amended answer to address this. The speed isn't going to be improved by doing it in a thread - still much better to open and handle exceptions IMO – Richard Harrison Sep 29 '13 at 15:32
  • You can call File.Exists(), just to check for existence of a file, without any intention of opening it. This can be useful in cross-application synchronization. I agree that checking for existence before opening is not recommended. – ThunderGr Dec 30 '13 at 15:09
  • 7
    Curious how long File.Exists may take if the file sits on a secondary HDD which is currently in stopped state and thus needs 5-10 seconds to spin up. AFAIK, Windows by default stops secondary drives for inactivity for 20 mins or so, I guess on notebooks on batter power this can happen even on the main drive. – Alex Mar 17 '15 at 19:36
  • 3
    I have to disagree on this one (yeah I know, 2 years later but it came up in something I was searching for >.<). You shouldn't be using exception handling to handle normal logic flow (i.e. it's not exceptional for a file not to exist UNLESS you've just checked that it does exist). That's almost always a bad design. The only time it's not is if there's no other way to do something, and even then that's usually relegated to poorly designed APIs. – Tyler W Aug 26 '15 at 22:30
  • 1
    @TylerW IMO that's a too rigid doctrine to apply without considering each case individually. File.Open can still fail and therefore you should handle exceptions. In a single threaded, single user application where you have sequential execution it is almost safe to call File.Exist before open/create however in any other scenario the only way to do this reliable is to open the file (in the right mode) and handle the exceptions properly. – Richard Harrison Sep 06 '15 at 12:19
  • 2
    @RichardHarrison I agree with Tyler W. You should use File.Exists to try and avoid creating an exception, of course you can't rely on the result, but exceptions should be just that - exceptions. Exceptions should be a secondary and not the primary mechanism for controlling the flow of logic within your code. Did you take into account the time taken to throw and catch an exception in your timings? – Mick Mar 16 '16 at 04:28
  • @Mick If not using exceptions is that important and you don't mind slower code then call File.Exists first. File.Exists requires an additional 0.12ms on a local. In this situation the only reliable option is to use the Exceptions - regardless. Source: http://pastebin.com/wRZfFfHM and http://share.linqpad.net/7en6b4.linq – Richard Harrison Mar 16 '16 at 09:20
  • @RichardHarrison And how long does it take to catch a thrown Exception by File.ReadAllText("An invalid path")? By my measure 136.5067 ms i.e.1137 times slower than File.Exists – Mick Mar 17 '16 at 00:04
  • 1
    @Mick I'm not saying don't use File.Exists. I'm saying when creating files, or reading files it may not be reliable and you'll have to catch the exception anyway. I just measured ReadAllText (missing file) http://share.linqpad.net/dt6qjq.linq at 0.051ms (check with File.Exists) versus 0.056ms (try catch). It can be a lot slower with a network share. Choose whatever is appropriate and use it. – Richard Harrison Mar 17 '16 at 06:22
  • 1
    One of the worst answers I've seen here. `File.OpenRead(@"\\offlineserver\share\test")` may block for 5-10 seconds till an exception occurs, same as `File.Exists()`; using a WebDav share may take dozen of seconds. On a server with heavy load without the availability of File.OpenAsync or File.ExistsAsync all thread pool threads may be locked up by such code and TPL will only create one additional thread per second if it detects total deadlock. – springy76 Mar 08 '17 at 12:05
  • @springy76 I hadn't considered the offline server case; so what is the correct solution? My main point is not to use File.Exists really for much; if you're going to read from or write to the file then just open it and dela with the exceptions, and obviously you could use OpenAsync or Open. – Richard Harrison Mar 09 '17 at 14:25
  • 1
    There is no File.OpenXyzAsync or FileInfo.OpenXyzAsync in .net 4.6.2. At the Win32 level even FileCreate itself is blocking. I don't have a correct solution, came to this while implementing a file system abstraction layer where some of the implementations could be using WebClient for external requests which would deal perfectly with TPL because there async methods are available. But the native implementation using FileInfo can tear down entire TPL because of thread pool starvation. As a workaround I created a custom thread which processes a queue of `TaskCompletionSource` objects. – springy76 Mar 10 '17 at 11:20
  • Worst thing is - Azure Web Apps are running by default on network drives - and not super reliable ones tbh. – Dirk Boer May 29 '20 at 09:47