如何在管道混合网络和文件系统IO中使用Rx.NET?

时间:2015-10-06 21:41:07

标签: c# system.reactive reactive-programming

我有以下要求:

  1. 从多个远程站点收集某些信息。
  2. 将信息序列化到磁盘。
  3. 联系相同的网站并确认已成功收集数据。
  4. 这是一个非常简化的流程,真正的流程也必须处理错误,还有其他方面,我认为这与我的问题无关,或者它似乎暂时无关。

    无论如何,这是我如何实现所描述的流程:

    var data = await GetSitesSource()
        .Select(site => Observable
            .FromAsync(() => GetInformationFromSiteAsync(site))
            .Select(site.MakeKeyValuePair))
        .Merge(maxConcurrentSiteRequests)
        .ToList();
    
    if (data.Count > 0)
    {
        var filePath = GetFilePath();
        using (var w = new StreamWriter(filePath))
        {
            await w.WriteAsync(YieldLines(data));
        }
        var tsUTC = DateTime.UtcNow;
        await data.ToObservable()
            .Select(o => Observable.FromAsync(() => AckInformationFromSiteAsync(o.Key, tsUTC, o.Value.InformationId)))
            .Merge(maxConcurrentSiteRequests);
    }
    

    其中:

    • MakeKeyValuePair是一种返回KeyValuePair<K,V>实例
    • 的扩展方法
    • YieldLinesdata转换为IEnumerable<string>
    • WriteAsync是一种虚构的扩展方法,将一系列字符串写入其StreamWriter

    这似乎不是一个好的实现,因为我没有利用这个事实,即我可以在第一个Merge运算符出来时开始写出记录。

    我可以使用SelectMany + Merge(1)运算符异步写出文件的块(顺序无关紧要),但是如何确保相应的StreamWriter被初始化只在需要时才妥善处理?因为如果没有数据,我甚至不想初始化StreamWriter

    我的问题 - 如何重写此代码,以便Observable管道在中间不会被中断以写出文件?它应包括所有三个阶段:

    1. 从多个网站获取数据
    2. 逐个编写数据,顺序无关紧要
    3. 写完所有数据后确认数据

1 个答案:

答案 0 :(得分:1)

我还没有对此进行过测试,但是您的代码都没有阻止它们加入。所以你可以这样做:

//The ToObservable extension for Task is only available through
using System.Reactive.Threading.Tasks;

GetSitesSource()
    .Select(site => Observable
        .FromAsync(() => GetInformationFromSiteAsync(site))
        .Select(site.MakeKeyValuePair))
    .Merge(maxConcurrentSiteRequests)
    .ToList()
    //Only proceed if we received data
    .Where(data => data.Count > 0)
    .SelectMany(data =>
      //Gives the StreamWriter the same lifetime as this Observable once it subscribes
      Observable.Using(
        () => new StreamWriter(GetFilePath()), 
        (w) => w.WriteAsync(YieldLines(data)).ToObservable()),
      //We are interested in the original data value, not the write result
      (data, _) => data)
    //Attach a timestamp of when data passed through here
    .Timestamp()
    .SelectMany(o=> {
      var ts = o.Timestamp;
      var data= o.Value;
      //This is actually returning IEnumerable<IObservable<T>> but merge
      //will implicitly handle it.
      return data.Select(i => Observable.FromAsync(() => 
                               AckInformationFromSiteAsync(i.Key, ts,
                                                           i.Value.InformationId)))
                .Merge(maxConcurrentSiteRequests);
    })
    //Handle the return values, fatal errors and the completion of the stream.
    .Subscribe();

更全面地回答您的问题

Using运算符将必须实现IDisposable的资源绑定到Observable的生命周期。第一个参数是一个工厂函数,当Observable订阅时将调用一次。