.NET Core 3.0中的IAsyncEnumerable有何特别之处?

本文的翻译是在“ C#开发人员”课程开始时准备的










.NET Core 3.0和C#8.0的最重要功能之一是新功能IAsyncEnumerable<T>(又名异步线程)。但是他有什么特别之处呢?现在我们该怎么办呢?



在本文中,我们将研究它IAsyncEnumerable<T>打算解决的任务,如何在我们自己的应用程序中实现它以及为什么在许多情况下IAsyncEnumerable<T>会取代它 查看.NET Core 3中的所有新功能Task<IEnumerable<T>>







以前的生活 IAsyncEnumerable<T>



解释为什么IAsyncEnumerable<T>它如此有用的最好方法也许是看一下我们之前面临的问题。



想象一下,我们正在创建一个用于与数据交互的库,并且我们需要一种从商店或API请求一些数据的方法。通常,此方法返回如下:Task<IEnumerable<T>>



public async Task<IEnumerable<Product>> GetAllProducts()


为了实现此方法,我们通常异步请求数据,并在数据完成后返回它。当我们需要进行多个异步调用以获取数据时,此问题变得更加明显。例如,我们的数据库或API可以返回整个页面中的数据,例如使用Azure Cosmos DB的以下实现:



public async Task<IEnumerable<Product>> GetAllProducts()
{
    Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);
    var iterator = container.GetItemQueryIterator<Product>("SELECT * FROM c");
    var products = new List<Product>();
    while (iterator.HasMoreResults)
    {
        foreach (var product in await iterator.ReadNextAsync())
        {
            products.Add(product);
        }
    }
    return products;
}


注意,我们在while循环中遍历所有结果,实例化产品对象,将它们放入List中,最后返回整个结果。这是非常低效的,尤其是在大型数据集上。



也许我们可以通过修改我们的方法来创建一个更有效的实现,以使它一次返回整个页面的结果:



public IEnumerable<Task<IEnumerable<Product>>> GetAllProducts()
{
    Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);
    var iterator = container.GetItemQueryIterator<Product>("SELECT * FROM c");
    while (iterator.HasMoreResults)
    {
        yield return iterator.ReadNextAsync().ContinueWith(t => 
        {
            return (IEnumerable<Product>)t.Result;
        });
    }
}


调用者将使用如下方法:



foreach (var productsTask in productsRepository.GetAllProducts())
{
    foreach (var product in await productsTask)
    {
        Console.WriteLine(product.Name);
    }
}


此实现效率更高,但是该方法现在返回从调用代码可以看出,调用方法和处理数据并不直观。更重要的是,分页是调用者不需要知道的数据访问方法的实现细节。IEnumerable<Task<IEnumerable<Product>>>



IAsyncEnumerable<T> 急于帮助



我们真正想要做的是异步地从数据库中获取数据,并在接收到结果时将结果传递回调用方。



在同步代码中,返回IEnumerable的方法可以使用yield return语句将每条数据从数据库返回给调用方。



public IEnumerable<Product> GetAllProducts()
{
    Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);
    var iterator = container.GetItemQueryIterator<Product>("SELECT * FROM c");
    while (iterator.HasMoreResults)
    {
        foreach (var product in iterator.ReadNextAsync().Result)
        {
            yield return product;
        }
    }
}


但是,千万不要这样做上面的代码将异步数据库调用转换为阻塞调用,并且无法扩展。



如果只有我们可以使用yield return异步方法!直到现在,这是不可能的。



IAsyncEnumerable<T>是在.NET Core 3(.NET Standard 2.1)中引入的。它提供了一个枚举器,该枚举器具有MoveNextAsync()可能是预期的方法这意味着启动器可以在接收结果的同时(中间)进行异步调用。现在,我们的方法可以返回并使用yield return传递数据



而不是返回Task <IEnumerable <T >> IAsyncEnumerable<T>



public async IAsyncEnumerable<Product> GetAllProducts()
{
    Container container = cosmosClient.GetContainer(DatabaseId, ContainerId);
    var iterator = container.GetItemQueryIterator<Product>("SELECT * FROM c");
    while (iterator.HasMoreResults)
    {
        foreach (var product in await iterator.ReadNextAsync())
        {
            yield return product;
        }
    }
}


要使用结果,我们需要使用await foreach()C#8中提供的新语法



await foreach (var product in productsRepository.GetAllProducts())
{
    Console.WriteLine(product);
}


这样好多了。该方法随其产生数据。调用代码以自己的速度使用数据。



IAsyncEnumerable<T> 和ASP.NET Core



.NET Core 3 Preview 7开始,ASP.NET可以从API控制器操作返回IAsyncEnumerable。这意味着我们可以直接返回方法的结果-有效地将数据从数据库传递到HTTP响应中。



[HttpGet]
public IAsyncEnumerable<Product> Get()
    => productsRepository.GetAllProducts();


更换Task<IEnumerable<T>>IAsyncEnumerable<T>



随着时间的流逝,随着我们对.NET Core 3和.NET Standard 2.1的更加熟悉,它有望IAsyncEnumerable<T>在我们通常使用Task <IEnumerable>的地方使用



我期待IAsyncEnumerable<T>在库中获得支持在本文中,我们看到了类似的代码使用Azure Cosmos DB 3.0 SDK查询数据:



var iterator = container.GetItemQueryIterator<Product>("SELECT * FROM c");
while (iterator.HasMoreResults)
{
    foreach (var product in await iterator.ReadNextAsync())
    {
        Console.WriteLine(product.Name);
    }
}


与前面的示例一样,本地Cosmos DB SDK也向我们加载了分页实现的详细信息,这使得处理查询结果变得困难。



要查看GetItemQueryIterator<Product>()返回它的外观IAsyncEnumerable<T>,我们可以在中创建一个扩展方法FeedIterator



public static class FeedIteratorExtensions
{
    public static async IAsyncEnumerable<T> ToAsyncEnumerable<T>(this FeedIterator<T> iterator)
    {
        while (iterator.HasMoreResults)
        {
            foreach(var item in await iterator.ReadNextAsync())
            {
                yield return item;
            }
        }
    }
}


现在,我们可以以更好的方式处理查询的结果:



var products = container
    .GetItemQueryIterator<Product>("SELECT * FROM c")
    .ToAsyncEnumerable();
await foreach (var product in products)
{
    Console.WriteLine(product.Name);
}


概要



IAsyncEnumerable<T>-是.NET的一个受欢迎的补充,在许多情况下,它将使您的代码更加令人愉悦和高效。您可以通过以下资源了解更多信息:








状态设计模式






阅读更多:






All Articles