使用Rx,我想观察一个公开方法GetItems
和事件NewItem
的旧对象。
当调用GetItems
时,它将同步返回它在缓存中的所有项目的列表。它还会产生异步提取的项目,这些项目将在收到时通过NewItem
事件发布。
如何通过制定LINQ查询以便捕获两个结果集,以一致的方式观察这两个源(sync + async)?生产订单并不重要。
答案 0 :(得分:2)
让我看看我是否理解了你的遗产。我假设它是一个通用类型,如下所示:
public class LegacyObject<T>
{
public IEnumerable<T> GetItems();
public event EventHandler<NewItemEventArgs<T>> NewItem;
}
使用这样的新项目事件:
public class NewItemEventArgs<T> : System.EventArgs
{
public T NewItem { get; private set; }
public NewItemEventArgs(T newItem)
{
this.NewItem = newItem;
}
}
现在,我为.ToObservable()
创建了LegacyObject<T>
扩展方法:
public static IObservable<T> ToObservable<T>(
this LegacyObject<T> @this)
{
return Observable.Create<T>(o =>
{
var gate = new object();
lock (gate)
{
var list = new List<T>();
var subject = new Subject<T>();
var newItems = Observable
.FromEventPattern<NewItemEventArgs<T>>(
h => @this.NewItem += h,
h => @this.NewItem -= h)
.Select(ep => ep.EventArgs.NewItem);
var inner = newItems.Subscribe(ni =>
{
lock (gate)
{
if (!list.Contains(ni))
{
list.Add(ni);
subject.OnNext(ni);
}
}
});
list.AddRange(@this.GetItems());
var outer = list.ToArray().ToObservable()
.Concat(subject).Subscribe(o);
return new CompositeDisposable(inner, outer);
}
});
}
此方法为每个订阅者创建一个新的observable - 在编写这样的扩展方法时,这是正确的做法。
它创建一个gate
对象来锁定对内部列表的访问。
因为您说调用GetItems
的行为会产生异步函数以获取新项目,所以我确保在调用NewItem
之前创建了GetItems
订阅。< / p>
inner
订阅会检查新项目是否在列表中,如果该项目不在列表中,则仅调用该项目的OnNext
。
对GetItems
进行了调用,并通过AddRange
将值添加到内部列表中。
在NewItem
事件开始在另一个线程上触发之前,不太可能但不太可能将这些项添加到列表中。这就是锁定访问列表的原因。 inner
订阅将在尝试将项目添加到列表之前等待它获得锁定,这将在将初始项目添加到列表后发生。
最后,内部列表变为可观察的,与主题连接,o
观察者订阅此可观察对象。
使用IDisposable
将两个订阅作为单个CompositeDisposable
返回。
这就是ToObservable
方法。
现在,我通过在遗留对象上创建一个构造函数来测试它,这个构造函数允许我传入可枚举值和可观察值。调用GetItems
并且observable驱动NewItem
事件时,将返回枚举。
所以,我的测试代码看起来像这样:
var tester = new Subject<int>();
var legacy = new LegacyObject<int>(new [] { 1, 2, 3, }, tester);
var values = legacy.ToObservable();
values.Subscribe(v => Console.WriteLine(v));
tester.OnNext(3);
tester.OnNext(4);
tester.OnNext(4);
tester.OnNext(5);
写入控制台的值是:
1
2
3
4
5
请告诉我这是否符合您的需求。
答案 1 :(得分:2)
如果我正确理解你的问题,可以使用以下内容完成:
legacyObject.GetItems().ToObservable()
.Merge(
Observable.FromEventPattern<IntEventArgs>(legacyObject, "NewItem")
.Select(e => e.EventArgs.Value));
编辑:Enigmativity在上述解决方案中发现了竞争条件(请参阅下面的评论)。希望能解决这个问题:
Observable.FromEventPattern<IntEventArgs>(legacyObject, "NewItem")
.Select(e => e.EventArgs.Value)
.Merge(Observable.Defer(() => legacyObject.GetItems().ToObservable()));
如果有帮助,这是我测试代码的其余部分。我不知道我是否准确地为你的遗产类建模了:
class IntEventArgs : EventArgs
{
public IntEventArgs(int value)
{
Value = value;
}
public int Value { get; private set; }
}
class Legacy
{
public event EventHandler<IntEventArgs> NewItem;
public IList<int> GetItems()
{
GenerateNewItemAsync();
return new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
}
private void GenerateNewItemAsync()
{
Task.Factory.StartNew(() =>
{
for (var i = 0; i < 100000; ++i)
{
var handler = NewItem;
if (handler != null) handler(this, new IntEventArgs(i));
Thread.Sleep(500);
}
});
}
}
class Example
{
static void Main()
{
var legacyObject = new Legacy();
legacyObject.GetItems().ToObservable()
.Merge(
Observable.FromEventPattern<IntEventArgs>(legacyObject, "NewItem")
.Select(e => e.EventArgs.Value))
.Subscribe(Console.WriteLine);
Console.ReadLine();
}
}