反应性扩展:修改处理缓慢的现有流

时间:2018-09-20 11:19:44

标签: rx-java system.reactive reactivex

我正在学习Rx,并试图将以下问题转换为Rx管道。似乎应该有一个简单的Rx解决方案,但是我找不到它。这是一些简单的C#代码来演示该问题:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Item = System.Collections.Generic.KeyValuePair<int, string>;

namespace Sample
{
    class Test
    {
        readonly object _sync = new object();

        readonly List<Item> _workList = new List<Item>();

        public void Update(IEnumerable<Item> items)
        {
            lock(_sync)
            {
                foreach (var item in items)
                {
                    bool found = false;
                    for (int i = 0; i < _workList.Count; ++i)
                    {
                        if (_workList[i].Key == item.Key)
                        {
                            _workList[i] = item;
                            found = true;
                            break;
                        }
                    }

                    if (!found)
                    {
                        _workList.Add(item);
                    }
                }
            }
        }

        public void Run()
        {
            void ThreadMethod(object _)
            {
                while (true)
                {
                    Item? item = null;

                    lock (_sync)
                    {
                        if (_workList.Any())
                        {
                            item = _workList[0];
                            _workList.RemoveAt(0);
                        }
                    }

                    if (item.HasValue)
                    {
                        var str = $"{item.Value.Key} : {item.Value.Value}";

                        Console.WriteLine($"Start {str}");
                        Thread.Sleep(5000); // simluate work
                        Console.WriteLine($"End {str}");
                    }
                }
            }

            var thread = new Thread(ThreadMethod);
            thread.Start();
        }
    }
}

“更新”事件由键/值对的列表组成。使用以下规则将更新与现有列表合并。 不能保证每个已知密钥都将出现在每次更新中

  • 如果找到该键,则将值替换为列表中当前位置。先前的值将被丢弃,不会被处理。
  • 如果找不到密钥,则该项目将添加到列表的末尾

一个单独的线程一次处理列表一个项目。此处理需要一些时间(由Thread.sleep模拟)。处理项目时,它们将从列表的开头删除。

如您所见,在处理单个项目期间,待办事项中的项目可能会发生适当的突变。关键是,每个密钥仅会处理收到的最新值,但积压中的密钥顺序无法更改(除非处理密钥时将其从列表中删除。如果将密钥重新引入列表中,则为添加到最后)。

我对Rx的最新尝试是将更新输入到“扫描”功能中,该功能将以前未知的键转换为主题,然后在组合所有最新值之前将每个键的新值输入其对应的主题,但效果不佳。

请不要讨论非Rx解决方案。上面的简单代码可以完成这项工作,但我想了解是否有Rx解决方案。

我正在使用C#(System.Reactive)工作,但我会很乐意接受Rx其他方言中的解决方案。

2 个答案:

答案 0 :(得分:1)

要实现目标,需要两种机制。第一个是可以为您提供已发射物品的最新值的地图。第二个是flatMap()运算符。

Map<String, String> currentSourceValue = new HashMap<>();

我使用String作为数据类型以及keyOf()valOf()方法。

此方法将使用最新值更新地图。如果已经有当前值,请替换它并返回empty()可观察值。

synchronized Observable<String> setLatestValue( String s ) {
    String r = currentSourceValue.put( keyOf( s ), valOf( s ) );
    return r == null ? Observable.just( s ) : Observable.empty();
}

如果可以发射该值,则该方法将从地图中提取该值。

synchronized Observable<String> getLatestValue( String s ) {
    String r = currentSourceValue.remove( keyOf( s ) );
    return r == null ? Observable.empty() : Observable.just( r );
}

这将允许发出最新值

source
 .flatMap( s -> setLatestValue( s ) )
 .observeOn( processingScheduler )
 .flatMap( s -> getLatestValue( s ), 1 )
 .subscribe( s -> process( s ) );

第一个flatMap()运算符更新传入流的最新值。如果队列中已经有该键的项目,则返回empty()观察值,以便在下游链中不占用任何空间。

第二个flatMap()运算符在处理线程上工作。 flatMap()的第二个参数表示应一次处理一项,而不能并行处理。如果地图中存在该值,它将发出一个值;如果地图中不存在该值,则将不显示任何值,并清除地图项。从理论上讲,第二个flatMap()可以发出一个值,但是当观察者链从一个线程跳到另一个线程时,存在一些不确定性。

synchronized关键字表示地图上的操作是原子操作,并防止将值从下游地图中删除,就像将其添加到上游地图中一样。

此解决方案的工作方式类似于groupBy()运算符,但是可以处理您只想处理给定键的最新值的情况。

答案 1 :(得分:1)

这将起作用,尽管我不是它的最大支持者。

我认为这是生产者/消费者的情况:一个线程创建工作,另一个线程创建工作。 producer主题代表添加工作的线程。其他所有事物都代表事物的消费者方面。如果您要class上一堂课,producer会上一堂课,其他都上一堂课。

completedKeys保留已完成的键,因此该键的状态会弹出:具有该键的新项目将移至该行的末尾。 readyGate代表何时有新的消费者来处理下一件事。将其与最新的工作内容相结合是棘手的部分。 WithLatestFrom效果很好,直到您得到一个空列表。 .Where().FirstAsync()很好地完成了等待的过程。

所有这些操作的关键是GroupByUntil:对事物进行分组,它们自然会落入首先添加密钥的顺序,这就是您想要的。 Until子句意味着我们可以关闭可观察对象,这将使带有旧键的新项目成为行尾。 DynamicCombinedLatest将所有这些可观察对象转换为一个列表,这实际上就是您的状态。

无论如何,这是您要去的

var producer = new Subject<Item>();
var readyGate = new Subject<Unit>();
var completedKeys = new Subject<int>();

var Process = new Action<Item>(kvp =>
{
    var str = $"{kvp.Key} : {kvp.Value}";

    Console.WriteLine($"Start {str}");
    Thread.Sleep(500); // simluate work
    Console.WriteLine($"End {str}");
});

var groups = producer
    .GroupByUntil(kvp => kvp.Key, kvp => kvp, go => completedKeys.Where(k => k == go.Key))
    .DynamicCombineLatest();

var q = groups.Publish(_groups => readyGate
        .ObserveOn(NewThreadScheduler.Default)
        .WithLatestFrom(groups, (_, l) => l)
        .SelectMany(l => l.Count == 0
            ? _groups.Where(g => g.Count > 0).FirstAsync()
            : Observable.Return(l)
        )
    )
    .Subscribe(l =>
    {
        var kvp = l[0];
        completedKeys.OnNext(kvp.Key);
        Process(kvp);
        readyGate.OnNext(Unit.Default);
    });


//Runner code:
producer.OnNext(new Item(1, "1-a"));
producer.OnNext(new Item(1, "1-b"));

producer.OnNext(new Item(2, "2-a"));
producer.OnNext(new Item(2, "2-b"));

readyGate.OnNext(Unit.Default);

await Task.Delay(TimeSpan.FromMilliseconds(100)); //to test if 1 gets done again and goes to the back of the line.
producer.OnNext(new Item(1, "1-c"));

DynamicCombinedLatest是这个(使用nuget包System.Collections.Immutable):

public static IObservable<List<T>> DynamicCombineLatest<T>(this IObservable<IObservable<T>> source)
{
    return source
        .SelectMany((o, i) => o.Materialize().Select(notification => (observableIndex: i, notification: notification)))
        .Scan((exception: (Exception)null, dict: ImmutableDictionary<int, T>.Empty), (state, t) => t.notification.Kind == NotificationKind.OnNext
            ? ((Exception)null, state.dict.SetItem(t.observableIndex, t.notification.Value))
            : t.notification.Kind == NotificationKind.OnCompleted
                ? ((Exception)null, state.dict.Remove(t.observableIndex))
                : (t.notification.Exception, state.dict)
        )
        .Select(t => t.exception == null
            ? Notification.CreateOnNext(t.dict)
            : Notification.CreateOnError<ImmutableDictionary<int, T>>(t.exception)
        )
        .Dematerialize()
        .Select(dict => dict.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value).ToList());
}