繁体   English   中英

GroupBy 如何与 IAsyncEnumerable 一起使用?

[英]How GroupBy works with IAsyncEnumerable?

我已经在有分页的HttpClient请求中实现IAsyncEnumerable ,但我还需要对它们进行GroupBy 所以我实现了如下代码;

public class Item 
{
   public int Id {get; set;}
   public string Name {get; set;}
}

public async IAsyncEnumerable<Item> GetItems()
{
   while(hasMorePage)
   {
       // ... get paginated items

       foreach(var item in paginatedItems)
       {
         yield return item;
       }
   }
}

// should find most repeated item(by Id) and count of them.
public async Task GroupItems()
{
  IAsyncEnumerable<Item> items = GetItems();
  
  //IAsyncGrouping
  await foreach(var item in items.GroupBy(i => i.Id).
                                  OrderByDescendingAwait(i => i.CountAsync()).
                                  Take(10))
 {
    Console.WriteLine(item.Key.ToString() + (await item.CountAsync()).ToString());
 }
}

正如我所料,这段代码工作得很好。 但我想了解GroupBy是如何在这里工作的,因为它应该有所有按 id 分组的项目有什么我想念的吗? 或者有什么我可以重构性能的吗?

首先,评论中链接的 ALinq repo 与 .NET 的 IAsyncEnumerable 或 System.Linq.Async 无关。 这是一个 8 年历史的存储库,甚至没有针对 .NET 核心。 System.Linq.Async 由为 .NET 构建 Reactive Excetions 的同一团队维护,其代码位于同一 Github 存储库

其次,不清楚需要解释什么行为。

  • GroupBy会阻止吗? 不,它没有。
  • GroupBy是否必须在产生结果之前消耗整个源? 是的,它确实。

如果您有长时间运行的 stream 事件,则必须等到 stream 结束才能获得任何结果。 这是因为 GroupBy 在其分配阶段计算分组,然后在其迭代阶段返回它们

protected override async ValueTask<bool> MoveNextCore()
{
    switch (_state)
    {
        case AsyncIteratorState.Allocated:
            _lookup = await Internal.Lookup<TKey, TSource>.CreateAsync(_source, _keySelector, _comparer, _cancellationToken).ConfigureAwait(false);
            _enumerator = _lookup.ApplyResultSelector(_resultSelector).GetEnumerator();
            _state = AsyncIteratorState.Iterating;
            goto case AsyncIteratorState.Iterating;

        case AsyncIteratorState.Iterating:
            if (_enumerator!.MoveNext())
            {
                _current = _enumerator.Current;
                return true;
            }

            await DisposeAsync().ConfigureAwait(false);
            break;
    }

    return false;
}

如果您想处理事件流,您应该查看 Rx.NET,它由创建 System.Linq.Async 的同一团队构建。 在 Rx.NET 中,当遇到新的键值时, GroupBy将发出一个新组 stream:

反应式扩展 GroupBy 插图

请注意,Rx.NET 的GroupBy实际上按分组键对事件 stream 进行分区,并发出而不是分组。 订阅者将订阅这些流并处理他们的事件。 聚合示例演示了这一点:

var source = Observable.Interval(TimeSpan.FromSeconds(0.1)).Take(10);
var group = source.GroupBy(i => i % 3);
group.Subscribe(
  grp => 
    grp.Min().Subscribe(
      minValue => 
        Console.WriteLine("{0} min value = {1}", grp.Key, minValue)),
  () => Console.WriteLine("Completed"));

如果您需要处理长时间运行的 IAsyncEnumerable<> stream 您可以使用ToObservable

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM