假设:
我使用以下简单代码来模拟现实生活:
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()));
我发现它存在多个问题:
GroupBy
在这里是错误的,因为数据已经是正确的顺序。它应该是Window
或Buffer
,但是由谓词而不是样本计数或时间间隔驱动。实现我想要的正确的Rx.NET方式是什么?
答案 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避免了并发性。但是,如果需要,可以在需要时引入调度程序来分配工作。
根据你的意见:
我的方法如下(可以粘贴到包含反应库的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;
}
执行此操作,您将看到我们为每个分组创建了一个新线程,然后我们在SelectMany中等待异步。