【问题标题】:Wait for a folder and its content to be completely copied等待文件夹及其内容被完全复制
【发布时间】:2018-11-06 13:20:29
【问题描述】:

我正在使用FileSystemWatcher 监视文件夹并检查是否出现新文件夹。然后我必须从其他地方复制一些文件。但我必须先等待文件夹被复制。这是代码。

bool waiting = true;
var watcher = new FileSystemWatcher(path);
watcher.Created += (obj, args) =>
{
    //do something
    waiting = false;        
};
watcher.NotifyFilter = NotifyFilters.DirectoryName;
watcher.EnableRaisingEvents = true;

while(waiting)
{

}

问题是,一旦创建了文件夹,我就会收到通知,即使文件夹尚未完全复制,我也会收到“做某事”部分,显然我遇到了问题。在“做某事”部分之前,我必须以某种方式等待文件夹完全复制。我该怎么做?

【问题讨论】:

  • 您无法知道内容何时被完全复制。创建一个大文件涉及一个创建事件和多个更改事件。没有“关闭”或“完成”事件。您可以做的是监听所有更改事件并等到它们安静下来。发生这种情况时,请复制引发这些事件的文件。
  • @JohnnyMopp 该链接没有可接受的答案,并且没有一个答案实际上回答了这个众所周知的问题。 Dropbox、OneDrive 和任何其他文件同步程序都面临同样的问题
  • @JohnnyMopp 如果我理解正确,仅用于完成 1 个文件,而不是用于文件夹中的所有文件
  • @Mr.Howell 处理此问题的另一种方法是执行备份软件的工作 - 启用和监控卷的日志并使用快照以一致的状态读取文件。不用说,这需要更高的特权。 API 只能通过互操作或 AlphaFS 等库获得。

标签: c# file-watcher


【解决方案1】:

这是所有文件同步应用程序(如 Dropbox、OneDrive 等)都面临的常见问题。复制大文件涉及一个创建事件和 多个 Changed 事件,因为文件无法在单次操作。没有Closed 事件,因此应用程序只能等到changed 事件停止后才能再次开始散列和同步。

事实上,您会注意到,当您将大量文件复制到 Dropbox 等监控的文件夹中时,他们会停止正在执行的操作,并在复制停止后稍等片刻。

Reactive Extensions 在 .NET、Java、Javascript 和其他语言中允许对事件流使用类似 LINQ 的查询。可用的运算符之一是Debounce,它会等到事件流安静下来后再发出最后一个事件。此运算符(在 .NET 中称为 Throttle)可用于检测文件创建何时停止。

此示例在最后一次创建文件后等待 5 秒,然后再调用订阅者方法:

using (var fsw = new FileSystemWatcher(@"K:\Backups"))
{
    fsw.InternalBufferSize = 65536;
    var creations = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
        h => fsw.Created += h,
        h => fsw.Created -= h);

    creations.Timestamp()
        .Throttle(TimeSpan.FromSeconds(5))
        .Select(x => $"{ x.Timestamp} : {DateTime.Now - x.Timestamp} -  {x.Value.EventArgs.FullPath}")
        .Subscribe(Console.WriteLine);
}

Timestamp 用于为每个事件添加一个Timestamp 属性,以演示文件创建和订阅者执行之间的时间差。

通过仅返回最后一个事件,这个单一的Throttle() 可用于表示整个文件夹的处理。要处理单个文件,我们需要分别限制每个文件生成的事件流。换句话说,按文件对事件进行分组:

var obs = from creation in creations
            group creation by creation.EventArgs.FullPath into g
            from last in g.Throttle(TimeSpan.FromSeconds(5))
            select last.EventArgs.FullPath;
obs.Subscribe(Console.WriteLine);

在这种情况下,LINQ 查询语法要容易得多。 group by 按文件名对事件进行分组,然后Throttle() 在静默 5 秒后发出每个文件的最后一个事件。

要使这项工作适用于大文件,我们需要将 Created 和 Changed 事件结合起来。这就是 Merge 运算符的工作:

var changes = Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
        h => fsw.Changed += h,
        h => fsw.Changed -= h)

var obs = from evt in creations.Merge(changes)
        group evt by evt.EventArgs.FullPath into g
        from last in g.Throttle(TimeSpan.FromSeconds(5))
        select $"{last.EventArgs.ChangeType} - {last.EventArgs.FullPath}";

事情就是这样轰隆隆!

在 Windows 10 上复制仅引发 两个更改事件,最后一个仅在复制完成时引发。如果文件太大(GB 或 100 MB,取决于磁盘速度),第二个事件可能需要很长时间才能到达。

一种选择是设置一个大的时间跨度,大到足以覆盖大多数 IO 操作,例如TimeSpan.FromMinutes(1)

另一种选择是使用另一个运算符 Buffer(),它可以批量捕获指定数量的项目并将它们作为数组返回:

var obs = from evt in creations.Merge(changes)
        group evt by evt.EventArgs.FullPath into g
        from last in g.Buffer(3)
        select $"{last[2].EventArgs.ChangeType} - {last[2].EventArgs.FullPath}";

这仅在复制时有效。当应用程序对文件进行多次更改时,例如从 Excel 或 Word 保存文件可能会导致多个 Changed 事件。

Buffer 也可以采用 Timespan 参数,该参数可用于收集每个文件的所有 Changed 事件并检查它们是否符合两种模式之一。多变?只是一个开始/结束 Changed 事件?最后一个事件是什么时候发生的(由 Timestamp 提供)?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-09
    • 1970-01-01
    • 2020-09-29
    • 1970-01-01
    • 2014-04-01
    • 2013-02-15
    • 2020-05-16
    相关资源
    最近更新 更多