在Rx中组合序列

时间:2014-02-14 01:31:06

标签: c#-4.0 system.reactive

我正在尝试将Rx方法应用于使用任务和事件的现有应用程序。感谢上一个问题,我取得了很大的进展,但现在有一个场景,我不知道如何解决Rx,所以我想我会征求意见。

情况如下:

步骤1)检索每个包含标识符的项目列表。这是一个有限的列表,它将完成(例如,Web服务调用返回数据)。我现在把它作为IObservable。

步骤2)对于步骤1)中的每个唯一标识符,从不同的源检索辅助信息。在单个呼叫中获取多个标识符的辅助信息(例如第二个Web服务)是可能且更有效的。

步骤3)将信息组合到单个IObservable

我不想在第2步拨打我已经请求过的电话。

我确信使用Rx必须有一个非常优雅的解决方案,但我还不熟悉Rx来确定它。任何想法都会受到欢迎。

此致 艾伦

2 个答案:

答案 0 :(得分:1)

多田:

GetAListOfThings()
    .SelectMany(list => GetSecondaryInformation(list)
        .Select(secInfo => new { list, secInfo }));

答案 1 :(得分:1)

好的,所以 - 我在保罗的回答中提示你所做的评论“刚接触Rx ......”

如果结果的初始列表在单个结果中作为List<T>一次性返回,则问题非常简单。所以我认为它不会 - 它会以一系列可能包含重复信息的项目的形式返回。

方案

这是设计的场景:服务返回公司名称流,可能有重复的名称。使用辅助服务,您需要查找每个公司的股票代码,但只需查看一次。如果可能,您希望批量查找以提高效率。对于每个公司,您希望使用来自第三方服务的股票代码来订阅它的价格。

您最终希望将公司名称流转换为包含所有公司组合价格代码的单个流。

我不确定在你的情况下或一般情况下接下来是多么实际,但这是一个有趣的转移,并且希望有教育意义!

模拟服务

以下是一些要调用的模拟服务实现 - 您应该能够将其转储到控制台应用程序的Program类中进行尝试。设置模拟这个有很多东西,但是使用所有这些的底部解决方案真的很短:

首先,这是一个StockInfo类型来捕获公司股票信息,任何一个小例子数据库:

public class StockInfo
{
    public static List<StockInfo> StockDatabase = new List<StockInfo> {
        new StockInfo { Symbol = "MSFT", Name = "Microsoft" },    
        new StockInfo { Symbol = "GOOG", Name = "Google" },
        new StockInfo { Symbol = "APPL", Name = "Apple" },
        new StockInfo { Symbol = "YHOO", Name = "Yahoo" },
        new StockInfo { Symbol = "DELL", Name = "Dell" },
    };

    public string Symbol { get; set; }        
    public string Name { get; set; }
}

这是一个服务方法,它采用名称列表并为其返回StockInfo列表:

public static Task<List<StockInfo>> GetStockInfo(IList<string> symbols)
{
    return Task.Run(() => 
    {
        var results = from s in symbols
                      join i in StockInfo.StockDatabase
                      on s equals i.Name
                      select i;

        return results.ToList();                  
    });
}  

这是一种保存给定StockInfo股票价格的类型:

public class StockPrice
{
    public StockInfo StockInfo { get; set; }
    public double Price { get; set; }

    public override string ToString()
    {
        return StockInfo.Symbol + ":" + Price;
    }
}

此服务调用以给定StockInfo的随机间隔返回价格流。注意我们将订阅请求转储到控制台;如果我们的解决方案有效,您将永远不会再看到同一公司的订阅请求:

private static Random random = new Random();

public static IObservable<StockPrice> GetPrices(StockInfo stockInfo)
{    
    return Observable.Create<StockPrice>(o =>
    {      
        Console.WriteLine("Subscribed for " + stockInfo.Symbol);

        return Scheduler.Default.ScheduleAsync(
            (Func<IScheduler, CancellationToken, Task>)(
            async (ctrl, ct) =>
        {
            double price = random.Next(1, 10);
            while(true)
            {
                await ctrl.Sleep(TimeSpan.FromSeconds(random.NextDouble() * 10));
                price += Math.Round(random.NextDouble() - 0.5d, 2);
                var stockPrice = new StockPrice {
                    StockInfo = stockInfo,
                    Price = price
                };
                o.OnNext(stockPrice);
            };  
        }));                       
    });
}

这是最初的服务电话,逐渐返回一些公司名称,有一些重复(这都是如此做作 - 我的意思是为什么你不在服务方面进行重复数据删除!):

public static IObservable<string> GetStockNames()
{
    var exampleResults = new List<string> {
        "Microsoft",
        "Google",
        "Apple",
        "Microsoft",
        "Google",
        "Yahoo",
        "Microsoft",
        "Apple",
        "Apple",
        "Dell"
    };

    return exampleResults.ToObservable()
        .Zip(Observable.Interval(TimeSpan.FromSeconds(1)), (x, _) => x);
}

解决方案

完成所有设置后,我们可以继续使用解决方案(将其添加到您的控制台应用的Main方法中):

首先,我们决定批量生产 - 我们将批量生产3家公司,但不要等待超过5秒钟才能到达。

const int batchSize = 3;
var maxWait = TimeSpan.FromSeconds(5);

现在我们获得公司名称流:

var names = GetStockNames();  

我们使用Distinct()来提取每个名称的第一个实例。然后,我们使用Buffer批量处理名称,最多使用batchSize个名称或等待最多maxWait次;以先到者为准(第2行)。然后,我们将每个缓冲区(列表)投影到我们的股票信息服务的查找中,并将结果列表转换为单个StockInfo对象的流。 (第3-5行),将整个事物扁平化为IObservable<StockInfo>。这将是StockInfo项的唯一流:

var uniqueCompanies = names.Distinct()
                    .Buffer(maxWait, batchSize)      
                    .SelectMany(nameBatch => GetStockInfo(nameBatch)
                                            .ToObservable()
                                            .SelectMany(info => info));

最后,我们可以将每个StockInfo投影到一个价格流中,并将该地块展平以获得单个组合价格流:

var getPrices = uniqueCompanies.SelectMany(info => GetPrices(info));

getPrices.Subscribe(Console.WriteLine);

输出将是随机数据的随机数据,如:

Subscribed for MSFT
Subscribed for GOOG
Subscribed for APPL
GOOG:6.37
MSFT:7.05
GOOG:7.12
APPL:5.34
Subscribed for YHOO
Subscribed for DELL
MSFT:7.35
YHOO:8.2

希望有用!