我正在使用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)
{
}
问题是创建文件夹后,我会收到通知,即使文件夹尚未完全复制,“执行某些操作”部分也会发生,很显然,我遇到了问题。我必须以某种方式等待文件夹完全复制,然后再执行“操作”部分。我该怎么办?
答案 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提供)?