寻找一种优雅的Rx.NET方式来实现某些数据处理

时间:2015-04-08 22:15:36

标签: c# asynchronous system.reactive

假设:

  1. 数据库作为数据来源
  2. 必须对数据进行分组和聚合,其中聚合过程必须在代码中完成并且是异步的。
  3. 我使用以下简单代码来模拟现实生活:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Reactive.Linq;
    using System.Reactive.Threading.Tasks;
    using System.Threading.Tasks;
    
    namespace ObservableTest
    {
        class Program
        {
            public class Result
            {
                public int Key;
                private int m_previous = -1;
    
                public async Task<Result> AggregateAsync(int x)
                {
                    return await Task.Run(async () =>
                    {
                        await Task.Delay(10);
                        Debug.Assert(m_previous < 0 ? x == Key : m_previous == x - 10);
                        m_previous = x;
                        return this;
                    });
                }
    
                public int Complete()
                {
                    Debug.Assert(m_previous / 10 == 9);
                    return Key;
                }
            }
    
            static void Main()
            {
                var obs = GetSource()
                    .GroupBy(x => x % 10)
                    .SelectMany(g => g.Aggregate(Observable.Return(new Result { Key = g.Key }), (resultObs, x) => resultObs.SelectMany(result => result.AggregateAsync(x).ToObservable()))
                    .Merge()
                    .Select(x => x.Complete()));
    
                obs.Subscribe(Console.WriteLine, () => Console.WriteLine("Press enter to exit ..."));
                Console.ReadLine();
            }
    
            static IObservable<int> GetSource()
            {
                return Enumerable.Range(0, 10).SelectMany(remainder => Enumerable.Range(0, 10).Select(i => 10 * i + remainder)).ToObservable();
            }
        }
    }
    

    GetSource按特定顺序返回0到99之间的数字。订单已经与分组所需的顺序相匹配。查看此方法,就好像它使用与ORDER BY匹配预期分组的SQL语句查询数据库一样。

    因此,有一个可观察的数据库内容我需要对其进行分组,异步聚合并用聚合结果替换每个组。

    这是我的解决方案(来自上面的代码):

    var obs = GetSource()
        .GroupBy(x => x % 10)
        .SelectMany(g => g.Aggregate(Observable.Return(new Result { Key = g.Key }), (resultObs, x) => resultObs.SelectMany(result => result.AggregateAsync(x).ToObservable()))
        .Merge()
        .Select(x => x.Complete()));
    

    我发现它存在多个问题:

    1. GroupBy在这里是错误的,因为数据已经是正确的顺序。它应该是WindowBuffer,但是由谓词而不是样本计数或时间间隔驱动。
    2. 异步聚合看起来很麻烦,因此我假设我也搞砸了它。
    3. 实现我想要的正确的Rx.NET方式是什么?

2 个答案:

答案 0 :(得分:0)

我不完全确定是否有正确的Rx方法来解决这个问题,但是当处理集合时,事情开始变得混乱,尤其是当需要添加,更新或删除项目时。

我写了DynamicData一个开源项目,专门处理操作集合。所以我对这个答案的免责声明是我对解决方案非常偏颇。

回到问题,我会像这样实例化一个可观察的缓存

var myCache = new SourceCache<MyObject, MyId>(myobject=>myobject.Id)

您现在可以观察缓存并应用运算符。要分组并应用某些变换,请执行以下操作

var mystream = myCache.Connect()
   .Group(myobject => // group value) //creates an observable a cache for each group
   .Transform((myGroup,key) => myGroup.Cache.Connect().QueryWhenChanged(query=> //aggregate result)
//now do something with the result

其中Transform是Rx Select运算符的重载。我之前在博客上发布了一个详细的解决方案,可能适合您的问题Aggregation Example

此缓存是线程安全的,您可以使用addorupdate和remove方法异步加载和更改它。

答案 1 :(得分:0)

默认情况下,RX避免了并发性。但是,如果需要,可以在需要时引入调度程序来分配工作。

根据你的意见:

  1. 我不相信使用GroupBy如果你想要一个谓词来驱动分区,那么这里很糟糕。
  2. 我的方法如下(可以粘贴到包含反应库的linqpad)。我仍然在努力用可观察的方式扭曲我的思想,但我相信这是一个很好的习语,因为微软在https://msdn.microsoft.com/en-us/library/hh242963%28v=vs.103%29.aspx(最后一个例子)也显示了

    void Main()
    
    {
    Console.WriteLine("starting on thread {0}",Thread.CurrentThread.ManagedThreadId);
    
    //GetSource()
    //.GroupBy(x => x % 10)
    var sharedSource = GetSource().Publish();
    
    var closingSignal = sharedSource.Where(MyPredicateFunc);
    
    sharedSource.Window(()=>closingSignal)
    .Select(x => x.ObserveOn(TaskPoolScheduler.Default))
    .SelectMany(g=>g.Aggregate(0, (s,i) =>ExpensiveAggregateFunctionNoTask(s,i)).SingleAsync())
    .Subscribe(i=>Console.WriteLine("Got {0} on thread {1}",i,Thread.CurrentThread.ManagedThreadId))
    ;
    
    sharedSource.Connect();
    
    }// Define other methods and classes here
    
    bool MyPredicateFunc(int i){
         return (i %10 == 0);
    }
    
    static IObservable<int> GetSource()
    {
       return Enumerable.Range(0, 10)
        .SelectMany(remainder => Enumerable.Range(0, 10).Select(i => 10 * i + remainder)).ToObservable();
    }
    
    
    int ExpensiveAggregateFunctionNoTask(int lastResult, int currentElement){
    var r = lastResult+currentElement;
    Console.WriteLine("Adding {0} and {1} on thread {2}", lastResult, currentElement, Thread.CurrentThread.ManagedThreadId);
    Task.Delay(250).Wait(); //simulate expensive operation
    return r;
    }
    
  3. 执行此操作,您将看到我们为每个分组创建了一个新线程,然后我们在SelectMany中等待异步。