如何使用Reactive Extensions(Rx)为具有取消支持的IObservable主题排队

时间:2012-06-19 12:42:32

标签: .net system.reactive reactive-programming

首先是一些背景,我编写了一个名为Duplicitiy(在github.com上)的开源.NET库,它使用FileSystemWatcher复制两个目录之间的所有文件更改。

我编写了一个FileSystemObservable类来实现IObservable<FileSystemChange>(使用FSWatcher来包装实际的FileSystemWatcher)。创建,修改或删除文件或目录时,使用Reactive Extensions通过Subject<FileSystemChange>发布更改。

然后我使用以下订阅订阅此observable。

 return observable
          .Buffer(() => observable.Throttle(TimeSpan.FromSeconds(2)).Timeout(TimeSpan.FromMinutes(1)))     
          .PrioritizeFileSystemChanges()           
          .SelectMany(x => x);

更改被缓冲,直到至少有2秒的时间段,最多1分钟没有任何变化。这是因为在删除目录时,FileSystemWatcher会通知所有包含的文件和目录。我们可以通过吞下目录中包含的更改来优化行为,只需删除订阅者中的父级即可。这由PrioritizeFileSystemChanges过滤器处理。它还允许我们忽略在缓冲区窗口中创建并随后删除的文件,再次减少目标上的IO操作。

虽然目前以一种天真的方式不支持失败/重试,但仍然有效。

但是我的问题是,此观察者的订阅者可能需要花费合理的时间来处理每个更改。例如,将大文件复制到慢速文件系统。当为当前正在复制的同一文件发生新的文件系统更改时,如何处理中止正在进行的操作。或者,如果文件包含在缓冲列表中但尚未完成,那么如何将其删除或排除?

我认为需要对原始observable进行另一次订阅,但我不确定如何最好地共享状态或修改挂起的任务?必须按接收顺序处理更改,这表示队列。但是,新的文件系统更改可能适用于需要取消或删除的排队操作。队列不是为无序删除而设计的。

例如,如果我们当前正在复制文件Foo\Bar.txt并且删除了Foo目录。然后,必须取消目录和所有子目录的任何正在进行或挂起的更改。这可能是任务并行库的一个用例,还是我可以采取一些反应式方法?

也会收到任何github拉取请求!

1 个答案:

答案 0 :(得分:1)

您似乎在这里有几个目标/问题:

  1. 删除由于以后更改而不再需要的早期更改。链接列表可能非常适合这种情况。它为一般队列使用和良好的项目删除性能提供了良好的性能。
  2. 取消因以后的更改而不再需要的正在进行的操作。这还包括需要重新启动的操作。这将要求您找到允许您取消正在进行的操作的库。 System.IO类不提供此类取消,因此您需要找到一个库或自己滚动。
  3. 这可能是任务并行库的一个用例,还是有一些我可以采取的反应式方法?你的措辞让我感到震惊,好像这里有一个或另一个选择,但是有没理由你不能把两者混在一起。您应该对文件更改的可观察性是一个很好的起点(RX)。正在进行的操作可能会被实现为采用CancellationToken并返回Task(TPL)的方法。
  4. 这里缺少的步骤似乎是如何从变化的“队列”转变为实际工作。基本上,订阅必须对更改进行排队(快速)并启动(慢速,异步)方法,如果它尚未运行,则“递归地”处理队列;类似的东西:

    'changes is your returned observable
    'toProcess is the "queue" of changes
    'processor holds information about and the task of the in-progress operation
    changes.Subscribe(Sub(c)
                         UpdateQueueWithChange(c, toProcess, processor)
                         If processor.Task.IsCompleted Then
                             ProcessNextChange(processor, toProcess)
                         End If
                      End Sub)
    

    ProcessNextChange是一种方法,它将获取队列中的下一个更改,启动操作,设置操作任务的回调以重新调用ProcessNextChange。如果没有留下任何更改,processor应该被赋予一个完成的任务,该任务不会重新调用ProcessNextChange。

    UpdateQueueWithChange将需要更新“队列”并在必要时取消正在进行的操作,由于任务完成将触发对ProcessNextChange的调用,这将启动下一个操作。

    如果您想取消对可观察更改的订阅的取消操作,我建议将订阅一次性放入CompositeDisposable以及存储SerialDisposable的{​​{1}} }(由CancellationDispoable更新并另外存储在ProcessNextChange)中,这是操作方法所需的processor的来源。 ProcessNextChange将检查SerialDisposable以查看它是否在启动操作之前已被处理。 CompositeDisposable将存储在某处以结束整个事情。

    CancellationToken