示例代码
async Task Main()
{
var entities = new double[] { 1, 2, 4, 8, 16, 32 }.ToObservable();
entities = Square(entities);
entities = Print(entities);
entities = Add(entities, 1);
entities = await SaveObservable(entities);
entities = Add(entities, 2);
await PrintAll(entities);
}
public IObservable<double> Square(IObservable<int> source)
=> source.Select(i => Math.Pow(i, 2.0));
public IObservable<double> Add(IObservable<double> source, double add)
=> source.Select(i => i + add);
public IObservable<T> Print<T>(IObservable<T> source)
=> source.Do(i => Console.WriteLine("Print: {0}", i));
public async Task<IObservable<T>> SaveObservable<T>(IObservable<T> source)
{
var last = await source.LastOrDefaultAsync();
Console.WriteLine("Collected observable and saving to the db. Last element is '{0}'", last);
// await database.SaveChangesAsync();
return source;
}
public async Task PrintAll(IObservable<double> source)
=> await source.ForEachAsync(i => Console.WriteLine("PrintAll: {0}", i));
想象一下,在我的SaveObservable
函数中,我想对数据库执行操作,例如保存批量添加到数据库上下文中的所有实体,以便我可以获取数据库引擎(例如SQL)来填充任何数据库生成的ID。
所以我希望这个函数做的是收集传入的可观察流,调用database.SaveChangesAsync然后基本上继续传入传入的流。
但是,如果我运行上面的代码,则会再次调用Print
函数。我不希望之前调用的observable的任何投影/映射再次运行。
我将如何实现这一目标?它甚至可能吗?
答案 0 :(得分:3)
首先,你的观点是:这可能最好用普通的,基于拉的编程完成。
至于你的代码,你有两个问题:
1)Observable就像项目管道。您的代码基本上设置了两个管道:
entities -> Square -> Print -> Add(1) -> SaveObservable
entities -> Square -> Print -> Add(1) -> Add(2) -> PrintAll
这就是为什么你看到Print
被叫两次的原因。如果你想看到它被调用一次,你可能希望管道做这样的事情:
entities -> Square -> Print -> Add(1) -> SaveObservable
\-> Add(2) -> PrintAll
2)然而,由于async
和IObservable
的混合,这说起来更容易。制作多线程管道的懒惰方法是在Add调用后拍一个.Publish().RefCount()
:
entities = Square(entities);
entities = Print(entities);
var entitiesAdded = Add(entities, 1).Publish().RefCount();
var _ = await SaveObservable(entitiesAdded);
entities = Add(entitiesAdded, 2);
await PrintAll(entities);
但这不起作用,因为Publish
使得observable很热,一旦在SaveObservable
中使用它,项目就不会再次发出。当所有孩子都已连接时,另一种方法是.Publish()
,然后是.Connect()
,但这与await
的效果不佳。
对您的代码起作用的方式是添加.Replay().RefCount()
,但它可能效果不佳,因为Rx必须设置一个内部缓冲区,该缓冲区包含订阅生命周期内的所有项目。
var entities = new double[] { 1, 2, 4, 8, 16, 32 }.ToObservable();
entities = Square(entities);
entities = Print(entities);
entities = Add(entities, 1).Replay().RefCount();
entities = await SaveObservable(entities);
entities = Add(entities, 2);
await PrintAll(entities);
有关热/冷可观测量的更多信息,Publish
,Connect
,Replay
和RefCount
,see here。
总结虽然:我建议您在没有Rx的情况下使用EF和TPL。
答案 1 :(得分:2)
您有两个订阅的原因是由于这两行:
var last = await source.LastOrDefaultAsync();
await source.ForEachAsync(i => Console.WriteLine("PrintAll: {0}", i));
两者都会导致订阅您的原始资源可观察资料。
第一个是有效的:
var last = await
new double[] { 1, 2, 4, 8, 16, 32 }
.ToObservable()
.Select(i => Math.Pow(i, 2.0))
.Do(i => Console.WriteLine("Print: {0}", i))
.Select(i => i + 1)
.LastOrDefaultAsync();
第二个是有效的:
await
new double[] { 1, 2, 4, 8, 16, 32 }
.ToObservable()
.Select(i => Math.Pow(i, 2.0))
.Do(i => Console.WriteLine("Print: {0}", i))
.Select(i => i + 1)
.Select(i => i + 2)
.ForEachAsync(i => Console.WriteLine("PrintAll: {0}", i));
此问题的答案是避免使用任何async
/ await
代码。无论如何,Rx处理所有同步工作远比TPL更好。
您也可以在不涉及任务的情况下await
观察。
也许以这种方式尝试你的代码:
async void Main()
{
var entities = new double[] { 1, 2, 4, 8, 16, 32 }.ToObservable();
entities = Square(entities);
entities = Print(entities);
entities = Add(entities, 1);
entities = SaveObservable(entities);
entities = Add(entities, 2);
await PrintAll(entities);
}
public IObservable<double> Square(IObservable<double> source)
=> source.Select(i => Math.Pow(i, 2.0));
public IObservable<double> Add(IObservable<double> source, double add)
=> source.Select(i => i + add);
public IObservable<T> Print<T>(IObservable<T> source)
=> source.Do(i => Console.WriteLine("Print: {0}", i));
public IObservable<T> SaveObservable<T>(IObservable<T> source)
{
return Observable.Create<T>(o =>
{
var last = default(T);
return
source
.Do(
t => last = t,
() =>
{
Console.WriteLine("Collected observable and saving to the db. Last element is '{0}'", last);
})
.Subscribe(o);
});
}
public IObservable<double> PrintAll(IObservable<double> source)
=> source.Do(i => Console.WriteLine("PrintAll: {0}", i));
这可以工作并产生以下输出:
Print: 1 PrintAll: 4 Print: 4 PrintAll: 7 Print: 16 PrintAll: 19 Print: 64 PrintAll: 67 Print: 256 PrintAll: 259 Print: 1024 PrintAll: 1027 Collected observable and saving to the db. Last element is '1025'
要在继续管道之前允许SaveObservable
完成,您需要为该方法添加.ToArray()
。这会将产生零个或多个值的IObservable<T>
更改为IObservable<T[]>
,从而生成一个包含零个或多个元素的数组。因此,它必须在继续前完成所有值。
尝试将SaveObservable
更改为:
public IObservable<T> SaveObservable<T>(IObservable<T> source)
{
return
source
.ToArray()
.Do(ts =>
{
var last = ts.Last();
Console.WriteLine("Collected observable and saving to the db. Last element is '{0}'", last);
})
.SelectMany(t => t);
}
现在输出:
Print: 1 Print: 4 Print: 16 Print: 64 Print: 256 Print: 1024 Collected observable and saving to the db. Last element is '1025' PrintAll: 4 PrintAll: 7 PrintAll: 19 PrintAll: 67 PrintAll: 259 PrintAll: 1027
现在代码实际上是这样的:
await
new double[] { 1, 2, 4, 8, 16, 32 }
.ToObservable()
.Select(i => Math.Pow(i, 2.0))
.Do(i => Console.WriteLine("Print: {0}", i))
.Select(i => i + 1)
.ToArray()
.Do(ts =>
{
var last = ts.Last();
Console.WriteLine("Collected observable and saving to the db. Last element is '{0}'", last);
})
.SelectMany(t => t)
.Select(i => i + 2)
.Do(i => Console.WriteLine("PrintAll: {0}", i));