C# 如何在项目准备就绪后立即解析IAsyncEnumerables列表

C# 如何在项目准备就绪后立即解析IAsyncEnumerables列表,c#,.net,.net-core,iasyncenumerable,C#,.net,.net Core,Iasyncenumerable,如果我向这个函数发送25个ID。第一个FindByIdsQuery需要5000毫秒。另外4个FindByIdsQuery需要100毫秒。那么这个解决方案将不会输出任何实体,直到5000ms之后。是否有任何解决方案可以在有人要输出时立即开始输出实体。或者,如果您可以在Task中使用Task.WhenAny执行类似的操作 需要明确的是:这5个查询中的任何一个都可能需要5000毫秒。问题是您的代码让它们等待。异步foreach在这里没有任何意义,因为-您不执行异步 您可以这样做: var entity

如果我向这个函数发送25个ID。第一个FindByIdsQuery需要5000毫秒。另外4个FindByIdsQuery需要100毫秒。那么这个解决方案将不会输出任何实体,直到5000ms之后。是否有任何解决方案可以在有人要输出时立即开始输出实体。或者,如果您可以在Task中使用Task.WhenAny执行类似的操作


需要明确的是:这5个查询中的任何一个都可能需要5000毫秒。

问题是您的代码让它们等待。异步foreach在这里没有任何意义,因为-您不执行异步

您可以这样做:

var entityList=splitIdsList.Selectx=>FindByIdsQueryx.ToList

这是查询中可以运行异步的部分,但它不是,因为您将整个结果集具体化为一个列表。然后在其上进行异步循环,但此时所有结果都已在内存中

获得异步的方法只是去掉ToList。将查询转储到foreach中,不要将其具体化到内存中。异步foreach应该命中ef级别的查询而不是查询结果,这样您就可以在从数据库获取信息时对其进行处理。托利斯有效地绕过了这一点


还要了解EF不能有效地处理多个id查找。唯一可能的方法是将它们放入一个数组并包含,这是一个SQL IN子句,对于较大的数字来说效率非常低,因为它强制进行表扫描。有效的SQL方法是将它们加载到具有统计信息的表值变量中,并使用联接,但在EF中无法实现这一点—这是限制之一。long-IN子句的SQL限制已经有很好的文档记录。EF端的限制没有,但它们仍然存在。

问题是您的代码让它们等待。异步foreach在这里没有任何意义,因为-您不执行异步

您可以这样做:

var entityList=splitIdsList.Selectx=>FindByIdsQueryx.ToList

这是查询中可以运行异步的部分,但它不是,因为您将整个结果集具体化为一个列表。然后在其上进行异步循环,但此时所有结果都已在内存中

获得异步的方法只是去掉ToList。将查询转储到foreach中,不要将其具体化到内存中。异步foreach应该命中ef级别的查询而不是查询结果,这样您就可以在从数据库获取信息时对其进行处理。托利斯有效地绕过了这一点


还要了解EF不能有效地处理多个id查找。唯一可能的方法是将它们放入一个数组并包含,这是一个SQL IN子句,对于较大的数字来说效率非常低,因为它强制进行表扫描。有效的SQL方法是将它们加载到具有统计信息的表值变量中,并使用联接,但在EF中无法实现这一点—这是限制之一。long-IN子句的SQL限制已经有很好的文档记录。EF方面的限制没有,但仍然存在。

从您的评论中,我理解您的问题。基本上你要找的是某种SelectMany操作符。该操作符将开始等待所有IAsyncEnumerables,并按它们出现的顺序返回项,而不管源异步枚举的顺序如何

我希望默认的AsyncEnumerable.SelectMany可以做到这一点,但我发现这不是真的。它遍历源枚举,然后遍历整个内部枚举,然后继续下一步。因此,我将许多变体拼凑在一起,它们同时正确地等待所有内部异步枚举。请注意,我不能保证正确性和安全性。没有错误处理


从你的评论中,我理解了你的问题。基本上你要找的是某种SelectMany操作符。该操作符将开始等待所有IAsyncEnumerables,并按它们出现的顺序返回项,而不管源异步枚举的顺序如何

我希望默认的AsyncEnumerable.SelectMany可以做到这一点,但我发现这不是真的。它遍历源枚举,然后遍历整个内部枚举,然后继续下一步。因此,我将许多变体拼凑在一起,它们同时正确地等待所有内部异步枚举。请注意,我不能保证正确性和安全性。没有错误处理


名单,分开?那是打字错误吗?对不起,我忘了包括拆分代码。但是没有,它被分割成5个ID的块。分割方法的签名是什么?在不知道签名的情况下,我们无法解析splitIdsList变量的类型,因为它被声明为var。@TheodorZoulias你说得很对!现在,我让您更容易理解split的功能!名单,分开?那是打字错误吗?对不起,我忘了包括拆分代码。但是没有,它被分割成5个ID的块。分割方法的签名是什么?如果不知道签名,我们无法解析
splitIdsList变量的类型,因为它被声明为var.@TheodorZoulias您是非常正确的!现在,我让您更容易理解split的功能!如果我不使用ToList,数据库调用将不会异步执行,因为Linq是惰性计算的,对吗?如果我只是删除ToList并运行相同的代码,那么当我到达枚举器中的该点时,它将只执行FindByIdsQueryx1。因为我当时需要等待它,所以它不会继续到下一个FindByIdsQueryx2,直到FindbyIdsQueryx1完成。因此,需要改变的不仅仅是删除ToList。如果我没记错的话,ToList将IAsyncEnumerable迭代器具体化为一个列表,而不是实际值。但是如果您将该列表保留在内存中,并在内存中进行筛选,那么开始异步循环操作是没有意义的。Async有助于绕过IO时间,而IO时间在内存中没有列表。如果我不使用ToList,数据库调用将不会异步执行,因为Linq是延迟计算的,对吗?如果我只是删除ToList并运行相同的代码,那么当我到达枚举器中的该点时,它将只执行FindByIdsQueryx1。因为我当时需要等待它,所以它不会继续到下一个FindByIdsQueryx2,直到FindbyIdsQueryx1完成。因此,需要改变的不仅仅是删除ToList。如果我没记错的话,ToList将IAsyncEnumerable迭代器具体化为一个列表,而不是实际值。但是如果您将该列表保留在内存中,并在内存中进行筛选,那么开始异步循环操作是没有意义的。Async有助于绕过IO时间—您在内存中没有针对列表工作的IO时间。很抱歉不善于描述此问题,但非常感谢您的解决方案!:很抱歉不善于描述问题,但非常感谢您的解决方案!:
public async IAsyncEnumerable<Entity> FindByIds(List<string> ids)
    {
        List<List<string>> splitIdsList = ids.Split(5);

        var entityList = splitIdsList.Select(x => FindByIdsQuery(x)).ToList();

        foreach (var entities in entityList)
        {
            await foreach (var entity in entities)
            {
                yield return entity;
            }
        }
    }

private async IAsyncEnumerable<Entity> FindByIdsQuery(List<string> ids)
    {
        var result = await Connection.QueryAsync(query, new {ids})

        foreach (var entity in result)
        {
            yield return entity;
        }
    }
/// <summary>
/// Starts all inner IAsyncEnumerable and returns items from all of them in order in which they come.
/// </summary>
public static async IAsyncEnumerable<TItem> SelectManyAsync<TItem>(IEnumerable<IAsyncEnumerable<TItem>> source)
{
    // get enumerators from all inner IAsyncEnumerable
    var enumerators = source.Select(x => x.GetAsyncEnumerator()).ToList();

    List<Task<(IAsyncEnumerator<TItem>, bool)>> runningTasks = new List<Task<(IAsyncEnumerator<TItem>, bool)>>();

    // start all inner IAsyncEnumerable
    foreach (var asyncEnumerator in enumerators)
    {
        runningTasks.Add(MoveNextWrapped(asyncEnumerator));
    }

    // while there are any running tasks
    while (runningTasks.Any())
    {
        // get next finished task and remove it from list
        var finishedTask = await Task.WhenAny(runningTasks);
        runningTasks.Remove(finishedTask);

        // get result from finished IAsyncEnumerable
        var result = await finishedTask;
        var asyncEnumerator = result.Item1;
        var hasItem = result.Item2;

        // if IAsyncEnumerable has item, return it and put it back as running for next item
        if (hasItem)
        {
            yield return asyncEnumerator.Current;

            runningTasks.Add(MoveNextWrapped(asyncEnumerator));
        }
    }

    // don't forget to dispose, should be in finally
    foreach (var asyncEnumerator in enumerators)
    {
        await asyncEnumerator.DisposeAsync();
    }
}

/// <summary>
/// Helper method that returns Task with tuple of IAsyncEnumerable and it's result of MoveNextAsync.
/// </summary>
private static async Task<(IAsyncEnumerator<TItem>, bool)> MoveNextWrapped<TItem>(IAsyncEnumerator<TItem> asyncEnumerator)
{
    var res = await asyncEnumerator.MoveNextAsync();
    return (asyncEnumerator, res);
}
    var entities = SelectManyAsync(splitIdsList.Select(x => FindByIdsQuery(x)));

    return entities;