对不起,如果标题不是很清楚,我想不出更好的事情......
我正在以IObservable<char>
的形式接收用户输入,并且我想将其转换为IObservable<char[]>
,每次用户停止输入超过1时对字符进行分组第二。因此,例如,如果输入如下:
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);
但是每次键入新字符时我都需要重置延迟...
答案 0 :(得分:7)
Buffer
和Throttle
就足够了。为了让它变热,您可以使用.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));
}
答案 1 :(得分:0)
好的,我找到了解决方案:
Func<IObservable<char>> bufferClosingSelector =
() =>
_input.Timeout(TimeSpan.FromSeconds(1))
.Catch(Observable.Return('\0'))
.Where(i => i == '\0');
_input.Buffer(bufferClosingSelector)
.ObserveOnDispatcher()
.Subscribe(OnCompleteInput);
基本上,只要发生超时,bufferClosingSelector
就会推送一些内容,这会关闭当前缓冲区。可能有一种更简单,更优雅的方式,但它有效......我愿意接受更好的建议;)
答案 2 :(得分:0)
前段时间我写了一篇扩展来做你想做的事 - BufferWithInactivity
。
这是:
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();
}
};
});
}
答案 3 :(得分:0)
这应该有效。它不像解决方案那么简洁,因为它通过类而不是扩展方法实现逻辑,但它可能是更好的方法。简而言之:每次获得char
时,将其添加到List
并(重新)启动一个将在一秒钟后到期的计时器;当计时器到期时,将List
作为一个数组通知我们的订户并重置状态,以便下次准备好。
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();
}
}