如何等待Observable Observables?

时间:2015-03-06 21:06:26

标签: c# async-await system.reactive

我有IObservable个对象,我想要异步转换为列表字典。

这是我的代码,其中GetSource返回IObservable

await GetSource(...)
  .GroupBy(o => o.SiteId)
  .ToDictionary(g => g.Key, g => g.ToList())

当然,这是错误的,因为结果是IDictionary<int, IObservable<IList<T>>>,但我需要IDictionary<int, IList<T>>

本质上,我需要await每一个IObservable<IList<T>>,但我不知道如何优雅地做到这一点,因为我相信Rx.NET是可能的。

有什么想法吗?

3 个答案:

答案 0 :(得分:3)

请注意,无论您做什么,在源完成之前,您都无法完成字典。因此,一种简单的方法是异步获取如下列表:

await GetSource(...).ToList();

,然后像IList<T>GroupBy一样继续生成ToDictionary - 注意到您现在正在使用这些运算符的IEnumerable<T>实现来完成列表。

这种方法假定GetSource()可观察事件中事件的数量和时间,以及组之间的分配使得在完成的流上计算组和字典的费用不高。因为最终你要返回一个完整的字典,它将保存在内存中,我们可能只考虑分组和创建字典条目的成本。与序列化传递冗长事件流相比,内存数据的速度非常快,因此我倾向于认为很少有这种方法完全没有问题。

如果它是禁止的,可能值得建立每个组并同时列出事件并且事件到达,在这种情况下你可以这样做:

await GetSource(...).GroupBy(x => x.SiteId)
                    .SelectMany(x => x.ToList())
                    .ToDictionary(x => x[0].SiteId);

请注意,ToDictionary keySelector 中列表的第一个元素将始终存在(否则不会实现任何组),因此这是安全的。它确实看起来有点奇怪,但它是我能想到的最简单的方法来获取密钥。

或作为通用功能:

async Task<IDictionary<TKey, IList<T>>> ToDictionaryOfLists<T, TKey>(
    IObservable<T> source,
    Func<T, TKey> keySelector)
{        
    return await source.GroupBy(keySelector)
                       .SelectMany(x => x.ToList())
                       .ToDictionary(x => keySelector(x[0]));           
}

假设一个班级:

public class Site
{
    public int SiteId { get; set; }
}

您可以使用:

var result = ToDictionaryOfLists(GetSource(...), x=> x.SiteId);

答案 1 :(得分:1)

编辑: 我刚刚了解到,返回Select(...)+ Merge()的Task可以用SelectMany(...)代替,所以更好的方法是:

IDictionary<int, IList<Site>> result = await GetSource()
.GroupBy(o => o.SiteId)
.SelectMany(async group => (group.Key, List: await group.ToList()))
.ToDictionary(group => group.Key, group => group.List);

原文:

我刚刚开始学习RX,但也许这样的东西就足够了:

IDictionary<int, IList<Site>> result = await GetSource()
.GroupBy(o => o.SiteId)
.Select(async group => (group.Key, List: await group.ToList()))
.Merge()
.ToDictionary(group => group.Key, group => group.List);

假设网站实施如下:

public class Site
{
   public int SiteId { get; set; }
   //rest of class
}

答案 2 :(得分:1)

似乎您实际上并不需要Dictionary,而是Lookup<TKey,TElement>

  

表示一组键,每个键都映射到一个或多个值。

通过使用内置的RX运算符Lookup,创建ToLookup很简单。通过使用标准LINQ运算符LookupConvertingDictionaryToDictionary列表也很简单。以下是将这两个运算符组合在一个扩展方法中的方法:

public static async Task<Dictionary<TKey, List<TSource>>> ToDictionaryOfLists<TSource, TKey>(
    IObservable<TSource> source, Func<TSource, TKey> keySelector)
{
    var lookup = await source.ToLookup(keySelector);
    return lookup.ToDictionary(g => g.Key, g => g.ToList());
}