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));
}

谢谢!但是,它并不完全比我的解决方案“简单;”谢谢,它工作得很好,而且更多