在我编写股票市场交易员IObserver
的过程中,我遇到了三个错误,主要是从Reactive Extensions
库中抛出。
我有以下CompanyInfo
课程:
public class CompanyInfo
{
public string Name { get; set; }
public double Value { get; set; }
}
IObservable<CompanyInfo>
名为StockMarket
:
public class StockMarket : IObservable<CompanyInfo>
我的Observer
如下所示:
public class StockTrader : IObserver<CompanyInfo>
{
public void OnCompleted()
{
Console.WriteLine("Market Closed");
}
public void OnError(Exception error)
{
Console.WriteLine(error);
}
public void OnNext(CompanyInfo value)
{
WriteStock(value);
}
private void WriteStock(CompanyInfo value) { ... }
}
我运行以下代码:
StockMarket market = GetStockMarket();
StockTrader trader = new StockTrader();
IObservable<CompanyInfo> differential = market //[F, 1], [S, 5], [S, 4], [F, 2]
.GroupBy(x => x.Name) //[F, 1], [F, 2]; [S, 5], [S, 4]
.SelectMany(x => x //4, 8, 2, 3
.Buffer(2, 1) //(4, 8), (8, 2), (2, 3), (3)
.SkipLast(1) //(4, 8), (8, 2), (2, 3)
.Select(y => new CompanyInfo //(+100%), (-75%), (+50%)
{
Name = x.Key,
Value = (y[1].Value - y[0].Value) / y[0].Value
}) //[F, +100%]; [S, -20%]
);
using (IDisposable subscription = differential.Subscribe(trader))
{
Observable.Wait(market);
}
发生以下三个错误之一:
以下ArgumentException
从Reactive Extensions
中抛出 :
System.ArgumentException:已添加具有相同键的项。 在System.ThrowHelper.ThrowArgumentException(ExceptionResource资源) 在System.Collections.Generic.Dictionary`2.Insert(TKey键,TValue值,布尔加法) 在System.Reactive.Linq.Observable.GroupBy&#39; 3 ._。OnNext(TSource值)
以下IndexOutOfRangeException
:
参数名称:index 在System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument参数,ExceptionResource资源) 在System.Collections.Generic.List&#39; 1.get_Item(Int32 index) 在StockMarketTests。&lt;&gt; c__DisplayClass0_0.b__2(IList&#39; 1年) 在System.Reactive.Linq.Observable.Select&#39; 2 ._。OnNext(TSource值)
偶尔Console
调整的文字(颜色应该是一致的):
什么可能导致这些古怪的症状?
答案 0 :(得分:6)
关于Reactive Extensions
概念的最大好处之一是能够订阅'发生'(IObservable
)'somewhere'< / em>并在此'occurrence'上应用面向对象的概念 - 这不必知道'某处'的位置。
这种方式Reactive Extensions
简化了event
面向编程和producer-consumer
problems很多 。
在不知道观察数据来源的情况下订阅IObservable
的能力会强制订阅者认为 通知是不可预测的 。换句话说,在观察IObservable
时,您应该假设通知可以同时发送 。
由于Reactive Externsions
的行为合约,IObservables
应该一次生成一个项目。通常,这就是发生的事情,但有时外部实施不遵循该合同。
让我们来看看这三个问题中的每一个:
GroupBy
不是线程安全的 GroupBy
的工作方式是返回IObservable<IGroupedObservable<T>>
,其OnNext
方法调用外IObservable
个OnNext
,IGroupedObservable<T>
与IGroupedObservable<T>
匹配当前通知。它是通过为Subject<T>
中的每个密钥保留一个Dictionary
(更确切地说是一个ConcurrentDictionary
)来实现的 - 这不足为奇 - not Buffer(2, 1)
。这意味着 两个邻近通知可能导致双重插入 。
Select
并非一个人 Select
的线程安全性由其提供的委托决定。在上述情况中,Select
方法提供的代理依赖于Buffer
将提供大小为2的列表这一事实。 Queue
包含{ {1}},这不是并发 ,因此当从多个线程迭代时 - Buffer
的{{1}}可以为我们提供一些意想不到的结果< / EM> 强>
如果Queue
提供Exception
y
,null
Queue
可能会因同一原因而被抛出的StockTrader
OnNext
可以在迭代时修改它。
最后但并非最不重要的是,即使您只进行基本观察,OnNext
的{{1}}方法也会在非NullReferenceException
中修改控制台,从而导致奇怪的文本布局
存在atomic operation方法,使您能够验证您是否订阅了线性 ,这意味着 不超过一次调用IObservable<CompanyInfo> differential = market //[F, 1], [S, 5], [S, 4], [F, 2]
.Synchronize()
.GroupBy(x => x.Name) //[F, 1], [F, 2]; [S, 5], [S, 4]
.SelectMany(x => x //4, 8, 2, 3
.Buffer(2, 1) //(4, 8), (8, 2), (2, 3), (3)
.SkipLast(1) //(4, 8), (8, 2), (2, 3)
.Select(y => new CompanyInfo //(+100%), (-75%), (+50%)
{
Name = x.Key,
Value = (y[1].Value - y[0].Value) / y[0].Value
})
); //[F, +100%]; [S, -20%]
方法可以同时发生 。
由于即使Synchronize
扩展方法也不是线程安全的,因此需要在链的开头调用IObservable<T>
方法:
Synchronize
请注意,Observable
会在您的查询中添加另一个代理SELECT * FROM news
JOIN firm ON IDFIRM = firm.ID
WHERE Block = 0
JOIN publisher ON id_publisher = publisher.ID
WHERE Block = 0
,因此会使查询慢一点,因此 您应该避免在不需要时使用它 强>
答案 1 :(得分:1)
您的代码问题不在于查询,也不在于Rx本身。问题可能来自您实际的StockMarket
或StockTrader
实施。
现在,问题很可能是由于您要为market
观察点创建两个订阅而导致问题。
当你这样写:
using (IDisposable subscription = differential.Subscribe(trader))
{
Observable.Wait(market);
}
...您正在获得market
的两个订阅。一个位于differential.Subscribe(trader)
,另一个位于Observable.Wait(market);
。
我怀疑两个并发订阅会导致您的问题,但如果没有看到StockMarket
的实施,我们无法说明它为什么会抛出。
这是实现您自己的可观察和观察者实现的危险。你应该避免这样做。最好使用标准Rx运算符构建的IObservable<CompanyInfo> CompanyValues { get; }
属性CompanyInfo
挂起。
您应始终避免阻止.Wait(...)
等操作。
作为快速测试,我会将当前Observable.Wait(market);
替换为具有足够长睡眠时间的Thread.Sleep(?)
,以查看您的代码是否有效。当然,您需要确保在后台计划程序上生成值(例如Scheduler.Default
)。
我运行此代码来测试您的查询:
public class CompanyInfo
{
public string Name { get; set; }
public double Value { get; set; }
}
public class StockTrader : IObserver<CompanyInfo>
{
public void OnCompleted()
{
Console.WriteLine("Market Closed");
}
public void OnError(Exception error)
{
Console.WriteLine(error);
}
public void OnNext(CompanyInfo value)
{
WriteStock(value);
}
private void WriteStock(CompanyInfo value) { Console.WriteLine($"{value.Name} = {value.Value}"); }
}
public class StockMarket : IObservable<CompanyInfo>
{
private CompanyInfo[] _values = new CompanyInfo[]
{
new CompanyInfo() { Name = "F", Value = 1 },
new CompanyInfo() { Name = "S", Value = 5 },
new CompanyInfo() { Name = "S", Value = 4 },
new CompanyInfo() { Name = "F", Value = 2 },
};
public IDisposable Subscribe(IObserver<CompanyInfo> observable)
{
return _values.ToObservable().ObserveOn(Scheduler.Default).Subscribe(observable);
}
}
......用这个:
StockMarket market = new StockMarket();
StockTrader trader = new StockTrader();
IObservable<CompanyInfo> differential = market //[F, 1], [S, 5], [S, 4], [F, 2]
.GroupBy(x => x.Name) //[F, 1], [F, 2]; [S, 5], [S, 4]
.SelectMany(x => x //4, 8, 2, 3
.Buffer(2, 1) //(4, 8), (8, 2), (2, 3), (3)
.SkipLast(1) //(4, 8), (8, 2), (2, 3)
.Select(y => new CompanyInfo //(+100%), (-75%), (+50%)
{
Name = x.Key,
Value = (y[1].Value - y[0].Value) / y[0].Value
}) //[F, +100%]; [S, -20%]
);
IDisposable subscription = differential.Subscribe(trader);
Thread.Sleep(10000);
我没有一次崩溃或造成任何例外。