Reactive Extensions:缓冲直到用户空闲

时间:2012-11-27 21:22:32

标签: c# system.reactive

我有一个程序,我正在接收事件并希望分批处理它们,以便在我处理当前批次时进入的所有项目都将出现在下一批中。

Rx中简单的TimeSpan和基于计数的缓冲区方法将为我提供多批项目,而不是给我一大堆已经进入的项目(如果订阅者花费的时间超过指定的TimeSpan或超过N项进来,N大于数。)

我查看了使用Func<IObservable<TBufferClosing>>IObservable<TBufferOpening> and Func<TBufferOpening, IObservable<TBufferClosing>>的更复杂的Buffer重载,但我找不到如何使用这些的示例,更不用说弄清楚如何将它们应用于我我想做。

4 个答案:

答案 0 :(得分:1)

这样做你想要的吗?

var xs = new Subject<int>();
var ys = new Subject<Unit>();

var zss =
    xs.Buffer(ys);

zss
    .ObserveOn(Scheduler.Default)
    .Subscribe(zs =>
    {
        Thread.Sleep(1000);
        Console.WriteLine(String.Join("-", zs));
        ys.OnNext(Unit.Default);
    });

ys.OnNext(Unit.Default);
xs.OnNext(1);
Thread.Sleep(200);
xs.OnNext(2);
Thread.Sleep(600);
xs.OnNext(3);
Thread.Sleep(400);
xs.OnNext(4);
Thread.Sleep(300);
xs.OnNext(5);
Thread.Sleep(900);
xs.OnNext(6);
Thread.Sleep(100);
xs.OnNext(7);
Thread.Sleep(1000);

我的结果:

1-2-3
4-5
6-7

答案 1 :(得分:1)

你需要什么来缓冲价值,然后是工人 准备好它要求当前缓冲区,然后重置它。这个可以 完成RX和任务

的组合
class TicTac<Stuff> {

    private TaskCompletionSource<List<Stuff>> Items = new TaskCompletionSource<List<Stuff>>();

    List<Stuff> in = new List<Stuff>();

    public void push(Stuff stuff){
        lock(this){
            if(in == null){
                in = new List<Stuff>();
                Items.SetResult(in);
            }
            in.Add(stuff);
        }
    }

    private void reset(){
        lock(this){
            Items = new TaskCompletionSource<List<Stuff>>();
            in = null;
        }
    }

    public async Task<List<Stuff>> Items(){
        List<Stuff> list = await Items.Task;
        reset();
        return list;
    }
}

然后

var tictac = new TicTac<double>();

IObservable<double> source = ....

source.Subscribe(x=>tictac.Push(x));

然后在你的工作人员

while(true){

    var items = await tictac.Items();

    Thread.Sleep(100);

    for each (item in items){
        Console.WriteLine(item);
    }

}

答案 2 :(得分:1)

我之前采用的方法是在DotPeek / Reflector中提取ObserveOn方法并采用它所具有的排队概念并使其适应我们的要求。例如,在具有快速滴答数据(如财务)的UI应用程序中,UI线程可能充斥着事件,有时它无法更快地更新。在这些情况下,我们希望删除除最后一个之外的所有事件(对于特定的工具)。在这种情况下,我们将ObserveOn的内部队列更改为单个值T(查找ObserveLatestOn(IScheduler))。在您的情况下,您需要队列,但是您想要推送整个队列而不仅仅是第一个值。这应该可以帮到你。

答案 3 :(得分:0)

@Enigmativity答案的扩展。我用它来解决问题:

public static IObservable<(Action ready, IReadOnlyList<T> values)> BufferUntilReady<T>(this IObservable<T> stream)
{
    var gate = new BehaviorSubject<Guid>(Guid.NewGuid());

    void Ready() => gate.OnNext(Guid.NewGuid());

    return stream.Publish(shared => shared
        .Buffer(gate.CombineLatest(shared, ValueTuple.Create)
            .DistinctUntilChanged(new AnyEqualityComparer<Guid, T>()))
        .Where(x => x.Any())
        .Select(x => ((Action) Ready, (IReadOnlyList<T>) x)));
}

public class AnyEqualityComparer<T1, T2> : IEqualityComparer<(T1 a, T2 b)>
{
    public bool Equals((T1 a, T2 b) x, (T1 a, T2 b) y) => Equals(x.a, y.a) || Equals(x.b, y.b);
    public int GetHashCode((T1 a, T2 b) obj) => throw new NotSupportedException();
}

当准备接收下一个缓冲区时,订阅者会收到一个Ready()函数,该函数将被调用。我不会在同一线程上观察每个缓冲区来避免循环,但是如果您需要在同一线程上处理每个缓冲区,我想您可以将它打乱。