.NET Core 3.0和C#8.0的最重要功能之一是新功能
IAsyncEnumerable<T>
(又名异步线程)。但是他有什么特别之处呢?现在我们该怎么办呢?
在本文中,我们将研究它
IAsyncEnumerable<
T>
打算解决的任务,如何在我们自己的应用程序中实现它以及为什么在许多情况下IAsyncEnumerable<
T>
会取代它。
查看.NET Core 3中的所有新功能Task<IEnumerable<
T>>
以前的生活 IAsyncEnumerable<
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>
急于帮助
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
IAsyncEnumerable<
T>从.NET Core 3 Preview 7开始,ASP.NET可以从API控制器操作返回IAsyncEnumerable。这意味着我们可以直接返回方法的结果-有效地将数据从数据库传递到HTTP响应中。
[HttpGet]
public IAsyncEnumerable<Product> Get()
=> productsRepository.GetAllProducts();
更换为Task<
IEnumerable<
T>>
IAsyncEnumerable<
T>
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的一个受欢迎的补充,在许多情况下,它将使您的代码更加令人愉悦和高效。您可以通过以下资源了解更多信息:
状态设计模式