【问题标题】:Waiting for system to delete file等待系统删除文件
【发布时间】:2020-11-03 20:50:19
【问题描述】:

我在删除文件后刷新文件列表时遇到问题。当我发出删除文件的命令时,由于刷新方法试图访问应该删除的文件,因此引发了异常。

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

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

效果很好。

我的问题是

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

【问题讨论】:

  • 我们能看到剩下的代码吗?另外,什么样的文件系统(本地 NTFS 或某种形式的 NFS)?无论如何,在 NTFS 上的大多数文件系统删除操作都是原子的。
  • 它在 NTFS 上。您对代码的哪一部分感兴趣。Delete 方法递归地删除目录中的所有文件和目录本身。我不认为这有关系,所以我说我需要删除一个文件......这是一回事,不是吗?
  • 一点也不。我会留下答案
  • @kr85 请看我的帖子,我刚刚更新了 FileSystemWatcher 的 MSDN 链接指向 .NET 4.0 而不是 1.1

标签: c# multithreading filesystems delete-file


【解决方案1】:

这对我有用:

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循环是不会进入的。

【讨论】:

  • fi.Delete(); 在使用中会抛出异常
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 如果这是在 NFS 分区上,我怀疑是这样,那么 FileSystemWatcher 可能不可靠:stackoverflow.com/questions/239988/…
    • @ChrisShain 我只在 NTFS 系统上使用过它,而且效果很好。虽然不确定 NFS
    • 我认为太复杂的解决方案
    • @Beatles1692 不,一点也不。总是有很多方法或解决问题。如果你想要一些优雅的东西,那么这就是你要走的路。
    • FileSystemWatcher 对我们来说肯定是好几年了; change 和 delete 事件有时会出错或不触发,导致我们不得不重新删除文件、重新执行操作等。这是相对的,但在大多数软件案例中,99% 的准确率还不够好。
    【解决方案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();
            }
        }
    }
    

    以及一些利用上述 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;
        }
    }
    

    【讨论】:

      【解决方案5】:

      我一直用这个:

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

      herehere

      【讨论】:

        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

          在 NTFS 上使用Directory.Delete 删除目录,特别是the overload that takes a 'recursive' boolean,从您的程序的角度来看应该是一个原子操作。无需自己手动递归。

          【讨论】:

          • 应该是,但不是。 Directory.Exists 有时会返回 true,尤其是当它是下一行时。更糟糕的是,Directory.Create 有时会在 Directory.Delete 之后快速调用时抛出。
          猜你喜欢
          • 1970-01-01
          • 2013-01-27
          • 2015-12-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多