Rx的目标是协调和编排来自各种来源的基于事件的异步计算,如社交网络、传感器、UI事件等。例如,建筑物周围的安全摄像头,以及当有人可能在建筑物附近时触发的运动传感器,会向我们发送最近摄像头的照片。Rx还可以统计包含选举候选人姓名的推文,以估计候选人的受欢迎程度。这是通过以异步方式调用外部web服务来完成的。对于这些场景和其他类似的场景,编排往往会导致复杂的程序,正如你所看到的,Rx肯定会减轻这种努力。
在本章中,您将查看一个示例,了解使用和不使用Rx对应用程序的结构、可读性以及扩展和发展的容易程度有何影响。想象一下,你收到了Stocks R Us公司著名首席技术官Penny先生的一封信。Stocks R Us是一家股票交易公司,为客户提供资金投资和从收益中选择利息的建议。这就是为什么对公司来说,对股票市场的变化做出快速反应很重要。最近,Stocks R Us发现,它可以通过使用一个系统来节省资金,该系统可以为经历了剧烈变化的股票提供警报,正如Penny先生所说。Penny先生对剧烈变化的定义是超过10%的价格变化。当这些变化发生时,Stocks R Us希望尽快知道,以便通过出售或购买股票做出反应。
Penny先生来找你是因为他知道他可以指望你快速提交高质量的申请。您的工作(也是本章的目标)是创建一个应用程序,向用户通知经历剧烈变化的股票。当股票价值在两次读数之间增加或减少一定阈值(在这种情况下为10%)时,就会发生剧烈变化。当这种情况发生时,您希望通过向用户的手机发送推送通知或在应用程序的屏幕上显示警报(例如,显示红色闪烁条)来通知用户。
在本章的第一部分中,您将探索使用传统方法创建应用程序时通常会发生的步骤.NET事件方法。然后,我们将分析解决方案并讨论其弱点。
本章的第二部分将Rx介绍到您的应用程序中。您将首先将库添加到项目中,然后逐步以Rx样式制作Stocks R Us的应用程序。
股票信息来自股票交易来源,许多服务都提供这些信息。每个都有自己的API和数据格式,其中一些来源是免费的,比如雅虎财经(http://finance.yahoo.com)以及谷歌金融www.Google.com/Finance)。对于您的应用程序,最重要的属性是股票的报价符号和价格。股票的报价符号是一系列唯一标识交易股票或股票的字符(例如,MSFT是Microsoft股票符号)。
图2.1中的流程图描述了应用程序的逻辑流程。
图2.1库存回收单元应用程序逻辑的流程图。我们通知用户发生剧烈变化——价格变化超过10%。
对于应用程序接收到的每一条股票信息,它都会将股票的价差计算为新价格和先前价格之间的变化率。假设你收到一个更新,MSFT的价格从50美元变为40美元,变化了20%。这被认为是一个剧烈的更改,并导致在应用程序中显示警报。
在现实生活中,股票点的到达速度是可变的。目前,为了避免混淆你,你可以假设股票点以恒定的速度到达;你稍后会处理时间方面的问题。
为了保持股票信息的来源摘要,它通过类StockTicker公开。该类只公开一个关于StockTick的事件,每当有关于股票的新信息可用时就会引发该事件。
清单2.1 StockTicker类
class StockTicker
{
public event EventHandler<StockTick> StockTick;
}
StockTick类保存有关股票的信息,例如其报价符号和价格。
清单2.2 StockTick类
class StockTick
{
public string QuoteSymbol { get; set; }
public decimal Price { get; set; }
//other properties
}
你通常会看到传统的.NET事件。当需要向应用程序提供通知时.NET是将数据传递到应用程序的标准方式。要处理股票报价,您将创建一个StockMonitor类,该类将通过+=运算符连接到StockTick事件来侦听股票变化。
清单2.3 StockMonitor类
class StockMonitor
{
public StockMonitor(StockTicker ticker)
{
ticker.StockTick += OnStockTick; //每次引发事件时都会调用OnStockTick方法。
}
...
//rest of the code
}
该示例的核心是OnStockTick方法。在这里,如果你已经有了以前的股票报价,你将检查每个股票报价,以便将新价格与旧价格进行比较。为此,您需要一个容器来保存有关以前股票点的所有信息。因为每个刻度都包含QuoteSymbol,所以使用字典来保存这些信息是有意义的,并将QuoteSymol作为关键字。要保存有关先前tick的信息,请定义一个名为StockInfo的新类(清单2.4),然后可以在StockMonitor类中声明字典成员(清单2.5)。
清单2.4 StockInfo类
class StockInfo
{
public StockInfo(string symbol, decimal price)
{
Symbol = symbol;
PrevPrice = price;
}
public string Symbol { get; set; }
public decimal PrevPrice { get; set; }
}
每次使用新股票点调用OnStockTick时,应用程序都需要检查旧价格是否已保存到词典中。如果您要查找的键存在于字典中,则使用TryGetValue方法返回true,然后使用存储在该键下的值设置out参数。
清单2.5检查股票是否存在的OnStockTick事件处理程序
Dictionary<string,StockInfo> _stockInfos=new Dictionary<string, StockInfo>();
void OnStockTick(object sender, StockTick stockTick)
{
StockInfo stockInfo ;
var quoteSymbol = stockTick.QuoteSymbol;
var stockInfoExists = _stockInfos.TryGetValue(quoteSymbol, out stockInfo);
...
}
如果股票信息存在,您可以检查股票的当前和以前的价格,如以下列表所示,以查看变化是否大于定义剧烈变化的阈值。
清单2.6处理剧烈价格变化的OnStockTick事件处理程序
const decimal maxChangeRatio = 0.1m;
...
var quoteSymbol = stockTick.QuoteSymbol;
var stockInfoExists = _stockInfos.TryGetValue(quoteSymbol, out stockInfo);
if (stockInfoExists)
{
//stockInfo变量保存有关股票的信息;因为stockInfoExists是true,所以您可以确定stockInfo不是null。
var priceDiff = stockTick.Price-stockInfo.PrevPrice;
//变化的百分比
var changeRatio = Math.Abs(priceDiff/stockInfo.PrevPrice);
if (changeRatio > maxChangeRatio)
{
//Do something with the stock – notify users or display on screen
Console.WriteLine("Stock:{0} has changed with {1} ratio,
Old Price:{2} New Price:{3}",
quoteSymbol,
changeRatio,
stockInfo.PrevPrice,
stockTick.Price);
}
//为下一个事件保存价格。
_stockInfos[quoteSymbol].PrevPrice = stockTick.Price;
}
如果库存信息不在字典中(因为这是你第一次打勾),你需要用
_stockInfos[quoteSymbol]=new StockInfo(quoteSymbol,stockTick.Price);
当不再需要更新时(例如,当用户决定停止接收通知或关闭页面时),您需要使用-=运算符从事件中注销。但是你应该在哪里做呢?一种选择是在StockMonitor类中创建一个方法,当您想要停止时可以调用该方法。但幸运的是。NET通过实现IDisposable接口提供了一种处理这种类型的“清理”的机制,该接口包括用于释放资源的单个方法Dispose。这是它在StockMonitor中的样子:
public void Dispose()
{
_ticker.StockTick -= OnStockTick;
_stockInfos.Clear();
}
完整的代码如清单2.7所示。我在以下系列中运行了它:
Symbol: “MSFT” Price: 100
Symbol: “INTC” Price: 150
Symbol: “MSFT” Price: 170
Symbol: “MSFT” Price: 195
我得到了以下结果:
Stock:MSFT has changed with 0.7 ratio, Old Price:100 New Price:170
Stock:MSFT has changed with 0.15 ratio, Old Price:170 New Price:195.5
清单2.7 StockMonitor完整代码
class StockMonitor : IDisposable
{
private readonly StockTicker _ticker;
Dictionary<string, StockInfo> _stockInfos =
new Dictionary<string, StockInfo>();
public StockMonitor(StockTicker ticker)
{
_ticker = ticker;
ticker.StockTick += OnStockTick;
}
void OnStockTick(object sender, StockTick stockTick)
{
const decimal maxChangeRatio = 0.1m;
StockInfo stockInfo;
var quoteSymbol = stockTick.QuoteSymbol;
var stockInfoExists =
_stockInfos.TryGetValue(quoteSymbol, out stockInfo);
if (stockInfoExists)
{
var priceDiff = stockTick.Price - stockInfo.PrevPrice;
var changeRatio = Math.Abs(priceDiff / stockInfo.PrevPrice);
if (changeRatio > maxChangeRatio)
{
Debug.WriteLine("Stock:{0} has changed with {1} ratio OldPrice:{ 2}newPrice: { 3}", quoteSymbol,changeRatio, stockInfo.PrevPrice, stockTick.Price);
}
_stockInfos[quoteSymbol].PrevPrice = stockTick.Price;
}
else
{
_stockInfos[quoteSymbol] =
new StockInfo(quoteSymbol, stockTick.Price);
}
}
public void Dispose()
{
_ticker.StockTick -= OnStockTick;
_stockInfos.Clear();
}
}
Penny先生很满意,Stock R Us的工作人员正在使用该应用程序,其效果已经显示在他们的报告中。该应用程序接收股票更新,可以计算新旧价格之间的差额比率,并向用户发送警报。
就像生活中的一切一样,变化是不可避免的,Stocks R Us决定改变其股票信息来源。幸运的是,您使用StockTicker类抽象了源代码,因此StockTicker是唯一需要更改的类。
源代码更改后,您开始收到有关崩溃和其他错误的投诉,如丢失警报或不必要的警报。因此,您开始研究这个问题,并发现它与并发性有关。
——未完待续
——重庆教主(QQ23611316) 2024.05.16
——WPF中文网 wpfsoft.com