C# 在Rx中,如何在一段时间后对最新项目进行分组?
对不起,如果标题不是很清楚,我想不出更好的了 我正在以C# 在Rx中,如何在一段时间后对最新项目进行分组?,c#,system.reactive,C#,System.reactive,对不起,如果标题不是很清楚,我想不出更好的了 我正在以IObservable的形式接收用户输入,我想通过在用户每次停止键入超过1秒时对字符进行分组,将其转换为IObservable。例如,如果输入如下所示: h e l l o (pause) w o r l d (pause) ! (pause) 我希望可观察的输出为: ['h', 'e', 'l', 'l', 'o'] ['w', 'o', 'r', 'l', 'd'] ['!'] 我怀疑解决方案相当简单,但我找不到正确的方法。。。我试图
IObservable
的形式接收用户输入,我想通过在用户每次停止键入超过1秒时对字符进行分组,将其转换为IObservable
。例如,如果输入如下所示:
h
e
l
l
o
(pause)
w
o
r
l
d
(pause)
!
(pause)
我希望可观察的输出为:
['h', 'e', 'l', 'l', 'o']
['w', 'o', 'r', 'l', 'd']
['!']
我怀疑解决方案相当简单,但我找不到正确的方法。。。我试图使用Buffer
,GroupByUntil
,Throttle
和其他一些工具,但都没有用
任何想法都欢迎
编辑:我有一些几乎可以用的东西:
_input.Buffer(() => _input.Delay(TimeSpan.FromSeconds(1)))
.ObserveOnDispatcher()
.Subscribe(OnCompleteInput);
但我需要在每次键入新字符时重置延迟…好的,我找到了一个解决方案:
Func<IObservable<char>> bufferClosingSelector =
() =>
_input.Timeout(TimeSpan.FromSeconds(1))
.Catch(Observable.Return('\0'))
.Where(i => i == '\0');
_input.Buffer(bufferClosingSelector)
.ObserveOnDispatcher()
.Subscribe(OnCompleteInput);
Func缓冲关闭选择器=
() =>
_输入超时(TimeSpan.FromSeconds(1))
.Catch(可观察的.Return('\0'))
。其中(i=>i='\0');
_input.Buffer(bufferClosingSelector)
.ObserveOnDispatcher()
.订阅(完成输入);
基本上,
bufferClosingSelector
会在超时时推送某些内容,从而关闭当前缓冲区。可能有一种更简单、更优雅的方法,但它能起作用。。。我愿意接受更好的建议;) 我不久前写了一个扩展来完成您要做的事情-bufferwithinactive
这是:
public static IObservable<IEnumerable<T>> BufferWithInactivity<T>(
this IObservable<T> source,
TimeSpan inactivity,
int maximumBufferSize)
{
return Observable.Create<IEnumerable<T>>(o =>
{
var gate = new object();
var buffer = new List<T>();
var mutable = new SerialDisposable();
var subscription = (IDisposable)null;
var scheduler = Scheduler.ThreadPool;
Action dump = () =>
{
var bts = buffer.ToArray();
buffer = new List<T>();
if (o != null)
{
o.OnNext(bts);
}
};
Action dispose = () =>
{
if (subscription != null)
{
subscription.Dispose();
}
mutable.Dispose();
};
Action<Action<IObserver<IEnumerable<T>>>> onErrorOrCompleted =
onAction =>
{
lock (gate)
{
dispose();
dump();
if (o != null)
{
onAction(o);
}
}
};
Action<Exception> onError = ex =>
onErrorOrCompleted(x => x.OnError(ex));
Action onCompleted = () => onErrorOrCompleted(x => x.OnCompleted());
Action<T> onNext = t =>
{
lock (gate)
{
buffer.Add(t);
if (buffer.Count == maximumBufferSize)
{
dump();
mutable.Disposable = Disposable.Empty;
}
else
{
mutable.Disposable = scheduler.Schedule(inactivity, () =>
{
lock (gate)
{
dump();
}
});
}
}
};
subscription =
source
.ObserveOn(scheduler)
.Subscribe(onNext, onError, onCompleted);
return () =>
{
lock (gate)
{
o = null;
dispose();
}
};
});
}
不活动的公共静态IObservable缓冲区(
这是一个可观测的来源,
时间跨度不活动,
int最大缓冲区大小)
{
返回可观察的。创建(o=>
{
var gate=新对象();
var buffer=新列表();
var mutable=new SerialDisposable();
var subscription=(IDisposable)null;
var scheduler=scheduler.ThreadPool;
动作转储=()=>
{
var bts=buffer.ToArray();
buffer=新列表();
如果(o!=null)
{
o、 OnNext(bts);
}
};
操作处置=()=>
{
if(订阅!=null)
{
subscription.Dispose();
}
mutable.Dispose();
};
行动已完成=
onAction=>
{
锁(门)
{
处置();
dump();
如果(o!=null)
{
行动(o);
}
}
};
操作onError=ex=>
OnError或completed(x=>x.OnError(ex));
操作onCompleted=()=>onerror或completed(x=>x.onCompleted());
操作onNext=t=>
{
锁(门)
{
缓冲区。添加(t);
if(buffer.Count==maximumBufferSize)
{
dump();
可变。一次性=一次性。空;
}
其他的
{
mutable.Disposable=scheduler.Schedule(不活动,()=>
{
锁(门)
{
dump();
}
});
}
}
};
订阅=
来源
.ObserveOn(调度程序)
.认购(onNext、onError、onCompleted);
return()=>
{
锁(门)
{
o=零;
处置();
}
};
});
}
这应该行得通。它不像您的解决方案那样简洁,因为它通过类而不是扩展方法来实现逻辑,但这可能是一种更好的方法。简而言之:每次你得到一个字符
,把它添加到列表中
,然后(重新)启动一个将在一秒钟内过期的计时器;当计时器过期时,将列表作为数组通知我们的订阅者,并重置状态,以便为下次做好准备
class Breaker : IObservable<char[]>, IObserver<char>
{
List<IObserver<char[]>> observers = new List<IObserver<char[]>>();
List<char> currentChars;
DispatcherTimer t;
public Breaker(IObservable<char> source)
{
source.Subscribe(this);
t = new DispatcherTimer { Interval = new TimeSpan(0, 0, 1) };
t.Tick += TimerOver;
currentChars = new List<char>();
}
public IDisposable Subscribe(IObserver<char[]> observer)
{
observers.Add(observer);
return null; //TODO return a useful IDisposable
}
public void OnCompleted()
{
//TODO implement completion logic
}
public void OnError(Exception e)
{
//TODO implement error logic
}
public void OnNext(char value)
{
currentChars.Add(value);
t.Start();
}
void TimerOver(object sender, EventArgs e)
{
char[] chars = currentChars.ToArray();
foreach (var obs in observers)
obs.OnNext(chars);
currentChars.Clear();
t.Stop();
}
}
类断路器:IObservable,IObserver
{
列表观察员=新列表();
列出当前字符;
调度员t;
公共断路器(IObservable源)
{
来源:订阅(本);
t=新的调度程序{Interval=新的时间跨度(0,0,1)};
t、 Tick+=TimerOver;
currentChars=新列表();
}
公共IDisposable订阅(IObserver观察员)
{
添加观察员(观察员);
return null;//返回有用的IDisposable
}
未完成的公共无效()
{
//TODO实现完成逻辑
}
公共无效申报人(例外e)
{
//TODO实现错误逻辑
}
public void OnNext(字符值)
{
当前字符添加(值);
t、 Start();
}
无效TimerOver(对象发送器,事件参数e)
{
char[]chars=currentChars.ToArray();
foreach(观察员中的var obs)
obs.OnNext(chars);
currentChars.Clear();
t、 停止();
}
}
缓冲区
和节流阀
就足够了,如果您的震源是热的。要使其成为热门,您可以使用.Publish().RefCount()
来确保最终只订阅一个源
IObservable<IList<T>> BufferWithInactivity<T>(this IObservable<T> source,
TimeSpan dueTime)
{
if (source == null) throw new ArgumentNullException("source");
//defer dueTime checking to Throttle
var hot = source.Publish().RefCount();
return hot.Buffer(() => hot.Throttle(dueTime));
}
IObservable BufferwithInactive(此IObservable源,
时间跨度(双时间)
{
如果(source==null)抛出新的ArgumentNullException(“source”);
//将时间检查延迟到油门
var hot=source.Publish().RefCount();
返回热缓冲区(()=>hot.Throttle(dueTime));
}
谢谢!但是,它并不完全比我的解决方案“简单;”谢谢,它工作得很好,而且更多