答案是肯定的。先回答再解释:
public static class X
{
public static IObservable<T> GatedDebounce<T>(this IObservable<T> source, IObservable<bool> gating)
{
var finalStream = gating
.StartWith(false)
.DistinctUntilChanged()
.Publish(_gating => source.Publish(_source => Observable.Merge(
_source
.Window(_gating.Where(b => b), _ => _gating.Where(b => !b))
.SelectMany(o => o.LastAsync()),
_source
.Window(_gating.Where(b => !b), _ => _gating.Where(b => b))
.Merge()
)));
return finalStream;
}
}
然后,给定一个代表您的值的 IObservable<T> 和一个代表拖动开始和停止位置的 IObservable<bool>(true 表示拖动开始,false 表示拖动结束),您可以这样称呼它:
var throttledStream= valueStream.GatedDebounce(gateStream);
解释:
为了更好地理解它,让我们扔掉Publish 调用,并将其分解为多个部分:
第 1 部分,
source
.Window(gating.Where(b => b), _ => gating.Where(b => !b))
.SelectMany(o => o.LastAsync())
这个Window 函数意味着调用意味着我们在门控发出真时启动一个子集可观察(或窗口),并在门控发出假时结束该窗口。从该窗口中,我们选择最后一项(如果存在)。这只会在窗口关闭时发出。
第 2 部分,
source
.Window(gating.Where(b => !b), _ => gating.Where(b => b))
.Merge() //Equivalent to .SelectMany(o => o) if you prefer
这个Window 函数的作用正好相反:只要 gating 发出 false 就启动一个窗口,并且只要 gating 发出 true 就结束它。当它到达时,我们从那个窗口发出所有东西。
将这两者与Merge 放在一起,您就可以完成 90% 的解决方案。其余的:
-
.StartWith(false) 是为了确保我们在您最初启动 observable 时打开一个窗口,否则在第一个门控项目之前发生的值会丢失。
-
DistintUntilChanged() 是一种廉价的方法,可以确保我们的门是 t、f、t、f 并且绝不会连续两个相同的值,这会导致同时打开两个窗口。
-
Publish 调用是防止多次订阅的好习惯。您可以在此处的其他问答中找到更好的解释。