24

I had a problem with refreshing file list after deleting a file. When I gave command to delete file, the exception was thrown because the refresh method tried to access a file that was supposed to be deleted.

After some thought and debuging I came to conclusion that problem was in a fact that system needs some time to delete a file. And I solve it like this:

//Deleting file
System.Threading.Thread.Sleep(2000);
//Refreshing list

and it worked fine.

My question is

Is there a more elegant way to wait for system do delete file and then continue with code...?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Miro
  • 1,778
  • 6
  • 24
  • 42
  • 1
    Can we see the rest of the code? Also, what kind of filesystem (local NTFS or some form of NFS)? Most filesystem delete operations, on NTFS anyway, are atomic. – Chris Shain Feb 20 '12 at 23:31
  • It's on NTFS. What part of code are you interested in. Delete method recursively deletes all the files in the directory and the directory itself. I didn't thought that that's relevant so I said that I need to delete a file ... It's the same thing, isn't it? – Miro Feb 20 '12 at 23:43
  • 1
    Not at all. I'll leave an answer – Chris Shain Feb 20 '12 at 23:44
  • @kr85 Please see my post, I just updated the MSDN link of FileSystemWatcher to point to .NET 4.0 instead of 1.1 – GETah Feb 20 '12 at 23:56

7 Answers7

22

This works for me:

public static void DeleteFile(String fileToDelete)
{
    var fi = new System.IO.FileInfo(fileToDelete);
    if (fi.Exists)
    {
        fi.Delete();
        fi.Refresh();
        while (fi.Exists)
        {    System.Threading.Thread.Sleep(100);
             fi.Refresh();
        }
    }
}

I find that most of the time, the while loop will not be entered.

Pedro77
  • 5,176
  • 7
  • 61
  • 91
Mike G
  • 221
  • 2
  • 2
10

Lightweight code to use a FileSystemWatcher, subscribe to its Deleted event and wait.

void DeleteFileAndWait(string filepath, int timeout = 30000)
{
    using (var fw = new FileSystemWatcher(Path.GetDirectoryName(filepath), Path.GetFileName(filepath)))
    using (var mre = new ManualResetEventSlim())
    {
        fw.EnableRaisingEvents = true;
        fw.Deleted += (object sender, FileSystemEventArgs e) =>
        {
            mre.Set();
        };
        File.Delete(filepath);
        mre.Wait(timeout);
    }
}
JPK
  • 683
  • 6
  • 7
7

The most elegant way I can think of is using a FileSystemWatcher and subscribe to its Deleted event.

GETah
  • 20,922
  • 7
  • 61
  • 103
  • 3
    If this is on a NFS partition, as I suspect it is, then FileSystemWatcher may not be reliable: http://stackoverflow.com/questions/239988/filesystemwatcher-vs-polling-to-watch-for-changes – Chris Shain Feb 20 '12 at 23:40
  • 1
    @ChrisShain I have only used this on an NTFS system and it works just great. Not sure about NFS though – GETah Feb 20 '12 at 23:44
  • Too complicated solution I presume – Beatles1692 Feb 20 '12 at 23:49
  • 1
    @Beatles1692 No, not at all. There are always many ways or resolving a problem. If you want something elegant, then this is the way to go. – GETah Feb 20 '12 at 23:51
  • FileSystemWatcher definitely flakey for years for us; both the change and delete events sometimes misfire or don't fire, causing us to have to redrop files, reperform actions, etc. It's relative, but in most software cases, 99% accuracy isn't good enough. – galaxis Jun 10 '21 at 23:47
4

Here is some code using the FileWatcher. What we want to be able to do is

await Utils.DeleteDirectoryAsync("c:\temp\foo", recurse: true);

the below implements it

using System;
using System.IO;
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;

namespace Utils
{
    internal class FileWatcher : IDisposable
    {
        readonly FileSystemWatcher _Watcher;

        public Subject<FileSystemEventArgs> Changed = new Subject<FileSystemEventArgs>();

        public FileWatcher( string file )
        {
            // Create a new FileSystemWatcher and set its properties.
            _Watcher = new FileSystemWatcher
                       {
                           Path = Path.GetDirectoryName(file),
                           NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
                                          | NotifyFilters.FileName | NotifyFilters.DirectoryName,
                           Filter =Path.GetFileName(file) 
                       };

            // Add event handlers.
            _Watcher.Changed += OnChanged;
            _Watcher.Created += OnChanged;
            _Watcher.Deleted += OnChanged;
            _Watcher.Renamed += OnChanged;

            // Begin watching.
            _Watcher.EnableRaisingEvents = true;
        }

        // Define the event handlers.
        private void OnChanged( object source, FileSystemEventArgs e )
        {
            Changed.OnNext(e);
        }


        public void Dispose()
        {
            _Watcher.Dispose();
        }
    }
}

and some utils that take advantage of the above observable.

public static class FileUtils
{
    public static IObservable<FileSystemEventArgs> ChangedObservable(string path)
    {
        if (path == null)
            return Observable.Never<FileSystemEventArgs>();

        return Observable.Using(() => new FileWatcher(path), watcher => watcher.Changed);
    }

    public static Task DeleteDirectoryAsync(string path, bool recurse)
    {
        var task = new TaskCompletionSource<Unit>();

        if (Directory.Exists(path))
        {
            ChangedObservable(path)
                .Where(f => f.ChangeType == WatcherChangeTypes.Deleted)
                .Take(1)
                .Subscribe(v => task.SetResult(Unit.Default));

            Directory.Delete(path, recurse);
        }
        else
        {
            task.SetResult(Unit.Default);
        }

        return task.Task;
    }
}
bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217
1

Directory.Delete will throw an exception on the first error it encounters. If you want to continue deleting as many files and subdirectories as you can then you shouldn't use Directory.Delete and should write your own recursive delete with try/catch blocks inside the loops. An example when you might want to do that is if you are trying to cleanup temp files and one of the files has been locked.

Valerie
  • 11
  • 1
1

I always used this:

System.GC.Collect();
System.GC.WaitForPendingFinalizers();

See here and here

Martin Ba
  • 37,187
  • 33
  • 183
  • 337
Vadim
  • 107
  • 2
  • 8
-1

Deleting a directory using Directory.Delete, specifically the overload that takes a 'recursive' boolean, on NTFS, should be an atomic operation from your program's perspective. No need to recurse manually yourself.

Chris Shain
  • 50,833
  • 6
  • 93
  • 125
  • 3
    It should be, but it is not. Directory.Exists will sometimes return true, especially when it is the next line. Worse, Directory.Create will sometimes throw when called to quickly after Directory.Delete. – ILMTitan Jul 18 '17 at 00:29