等待系统删除文件

24

我在删除文件后刷新文件列表时遇到了问题。当我下达删除文件的命令时,会抛出异常,因为刷新方法试图访问应该已被删除的文件。

经过一番思考和调试,我得出结论,问题在于系统需要一些时间来删除文件。我是这样解决它的:

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

它运行得很好。

我的问题是

是否有更优雅的方法来等待系统删除文件,然后继续执行代码...?


1
我们能看到剩下的代码吗?此外,使用什么类型的文件系统(本地NTFS还是某种形式的NFS)?大多数文件系统删除操作,在NTFS上,都是原子操作。 - Chris Shain
它在NTFS上。你对哪部分代码感兴趣?Delete方法递归地删除目录中的所有文件和目录本身。我没有想到那很相关,所以我说我需要删除一个文件...这不是一样的吗? - Miro
1
完全不是。我会留下答案。 - Chris Shain
@kr85 请查看我的帖子,我刚刚更新了FileSystemWatcher的MSDN链接,将其指向.NET 4.0而不是1.1。 - GETah
7个回答

22

这对我行得通:

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();
        }
    }
}

我发现大多数时候 while 循环将不会被执行。


1
如果 fi.Delete(); 正在使用中,它会抛出一个异常。 - Mark Schultheiss

10

轻量级代码使用FileSystemWatcher,订阅其Deleted事件并等待。

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);
    }
}

7
我能想到的最优雅的方法是使用FileSystemWatcher并订阅其Deleted事件。

3
如果这是在一个NFS分区上,正如我怀疑的那样,那么FileSystemWatcher可能不可靠:https://dev59.com/nHVC5IYBdhLWcg3wnCaA - Chris Shain
1
@ChrisShain 我只在 NTFS 系统上使用过这个,它表现得非常好。不过我对 NFS 不确定。 - GETah
我猜这是一个太复杂的解决方案。 - Beatles1692
1
@Beatles1692 不,完全不是。解决问题总有很多方法。如果你想要优雅的解决方案,那么这就是正确的方式。 - GETah
FileSystemWatcher对我们来说多年来一直存在问题。更改和删除事件有时会误触发或不触发,导致我们必须重新放置文件,重新执行操作等。虽然这与场景相关,但在大多数软件情况下,99%的准确性是不够的。 - galaxis

4

这里是使用FileWatcher的一些代码。我们想要做的是:

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

下面是实现它的代码。
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();
        }
    }
}

还有一些工具可以利用上述可观察对象。

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;
    }
}

1

我一直使用这个:

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

请参见这里这里,这些内容与文件锁或垃圾回收出现问题有关。


1

Directory.Delete在遇到第一个错误时会抛出异常。如果你想要继续删除尽可能多的文件和子目录,那么就不应该使用Directory.Delete,而是应该编写自己的递归删除,并在循环内部使用try/catch块。一个需要这样做的例子是,当你试图清理临时文件并且其中一个文件被锁定时。


-1

使用Directory.Delete,特别是使用传入一个布尔值的重载方法,在NTFS上删除目录应该从程序的角度来看是一种原子操作。无需自己手动递归。


3
应该是这样的,但实际不是。Directory.Exists有时会返回true,特别是当它是下一行时。更糟糕的是,在Directory.Delete之后立即调用Directory.Create时,有时会抛出异常。 - ILMTitan

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