等待文件夹及其内容被完全复制

时间:2018-11-06 13:20:29

标签: c# file-watcher

我正在使用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)
{

}

问题是创建文件夹后,我会收到通知,即使文件夹尚未完全复制,“执行某些操作”部分也会发生,很显然,我遇到了问题。我必须以某种方式等待文件夹完全复制,然后再执行“操作”部分。我该怎么办?

1 个答案:

答案 0 :(得分:0)

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

实际上,您会注意到,当您将许多文件复制到Dropbox等监视的文件夹中时,它们会停止正在执行的操作,并在复制停止后稍等片刻。

.NET,Java,Javascript和其他语言中的

Reactive Extensions允许在事件流上使用类似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}";

这就是事情的结局 BOOM!

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

一种选择是设置一个较大的Timepan,使其足够覆盖大多数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事件,并检查它们是否适合两种模式之一。多个更改?只是开始/结束更改事件?上一次事件何时发生(由Timestamp提供)?