这是所有文件同步应用程序(如 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 提供)?