用于 .NET 的 Azure SDK 的分页功能
本文介绍如何使用用于 .NET 的 Azure SDK 的分页功能,有效且高效地处理大型数据集。 分页是将大型数据集划分为多个页面的操作,让使用者可更轻松地循环访问较小的数据量。 从 C# 8 开始,你可以使用异步流以异步方式创建和使用流。 异步流基于 IAsyncEnumerable<T> 接口。 用于 .NET 的 Azure SDK 通过其 AsyncPageable<T>
类公开 IAsyncEnumerable<T>
的实现。
本文中的所有示例都依赖于以下 NuGet 包:
- Azure.Security.KeyVault.Secrets
- Microsoft.Extensions.Azure
- Microsoft.Extensions.Hosting
- System.Linq.Async
有关用于 .NET 的 Azure SDK 包的最新目录,请参阅 Azure SDK 最新版本。
可分页返回类型
通过用于 .NET 的 Azure SDK 实例化的客户端可以返回以下可分页类型。
类型 | 说明 |
---|---|
Pageable<T> |
在页中检索的值的集合 |
AsyncPageable<T> |
在页中以异步方式检索的值的集合 |
本文中的大多数示例都是异步的,使用了 AsyncPageable<T>
类型的变体。 将异步编程用于 I/O 绑定操作是理想选择。 最佳用例是使用用于 .NET 的 Azure SDK 中的异步 API,因为这些操作代表 HTTP/S 网络调用。
使用 await foreach
循环访问 AsyncPageable
若要使用 await foreach
语法循环访问 AsyncPageable<T>
,请考虑以下示例:
async Task IterateSecretsWithAwaitForeachAsync()
{
AsyncPageable<SecretProperties> allSecrets = client.GetPropertiesOfSecretsAsync();
await foreach (SecretProperties secret in allSecrets)
{
Console.WriteLine($"IterateSecretsWithAwaitForeachAsync: {secret.Name}");
}
}
在前述 C# 代码中:
- 调用了 SecretClient.GetPropertiesOfSecretsAsync 方法并返回了一个
AsyncPageable<SecretProperties>
对象。 - 在
await foreach
循环中,每个SecretProperties
都以异步方式生成。 - 每个
secret
被具体化时,其Name
将写入控制台。
使用 while
循环访问 AsyncPageable
若要在 await foreach
语法不可用时循环访问 AsyncPageable<T>
,请使用 while
循环。
async Task IterateSecretsWithWhileLoopAsync()
{
AsyncPageable<SecretProperties> allSecrets = client.GetPropertiesOfSecretsAsync();
IAsyncEnumerator<SecretProperties> enumerator = allSecrets.GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
{
SecretProperties secret = enumerator.Current;
Console.WriteLine($"IterateSecretsWithWhileLoopAsync: {secret.Name}");
}
}
finally
{
await enumerator.DisposeAsync();
}
}
在前述 C# 代码中:
- 调用了 SecretClient.GetPropertiesOfSecretsAsync 方法,并返回了一个
AsyncPageable<SecretProperties>
对象。 - 调用了 AsyncPageable<T>.GetAsyncEnumerator 方法,返回了
IAsyncEnumerator<SecretProperties>
。 - 重复调用 MoveNextAsync() 方法,直到没有要返回的项。
循环访问 AsyncPageable
页
如果要控制从服务接收值的页面,请使用 AsyncPageable<T>.AsPages
方法:
async Task IterateSecretsAsPagesAsync()
{
AsyncPageable<SecretProperties> allSecrets = client.GetPropertiesOfSecretsAsync();
await foreach (Page<SecretProperties> page in allSecrets.AsPages())
{
foreach (SecretProperties secret in page.Values)
{
Console.WriteLine($"IterateSecretsAsPagesAsync: {secret.Name}");
}
// The continuation token that can be used in AsPages call to resume enumeration
Console.WriteLine(page.ContinuationToken);
}
}
在前述 C# 代码中:
- 调用了 SecretClient.GetPropertiesOfSecretsAsync 方法并返回了一个
AsyncPageable<SecretProperties>
对象。 - 调用了 AsyncPageable<T>.AsPages 方法并返回了
IAsyncEnumerable<Page<SecretProperties>>
。 - 使用
await foreach
对每个页面以异步方式进行循环访问。 - 每个页面都有一组 Page<T>.Values,表示使用同步
foreach
循环访问的IReadOnlyList<T>
。 - 每个页面还包含一个 Page<T>.ContinuationToken,可用于请求下一页。
将 System.Linq.Async
与 AsyncPageable
配合使用
System.Linq.Async
包提供一组对 IAsyncEnumerable<T> 类型执行操作的 LINQ 方法。 由于 AsyncPageable<T>
实现 IAsyncEnumerable<T>
,因此可使用 System.Linq.Async
查询和转换数据。
转换为 List<T>
使用 ToListAsync
将 AsyncPageable<T>
转换为 List<T>
。 如果数据未在单个页面中返回,此方法可能会导致多个服务调用。
async Task ToListAsync()
{
AsyncPageable<SecretProperties> allSecrets =
client.GetPropertiesOfSecretsAsync();
List<SecretProperties> secretList = await allSecrets.ToListAsync();
secretList.ForEach(secret =>
Console.WriteLine($"ToListAsync: {secret.Name}"));
}
在前述 C# 代码中:
- 调用了 SecretClient.GetPropertiesOfSecretsAsync 方法并返回了一个
AsyncPageable<SecretProperties>
对象。 ToListAsync
方法处于等待状态,该方法会将新的List<SecretProperties>
实例具体化。
采用前 N 个元素
Take
可用于仅获取 AsyncPageable
的前 N
个元素。 使用 Take
将使获取 N
个项所需的服务调用最少。
async Task TakeAsync(int count = 30)
{
AsyncPageable<SecretProperties> allSecrets =
client.GetPropertiesOfSecretsAsync();
await foreach (SecretProperties secret in allSecrets.Take(count))
{
Console.WriteLine($"TakeAsync: {secret.Name}");
}
}
更多方法
System.Linq.Async
提供其他方法,这些方法提供与其同步 Enumerable
对等项等效的功能。 此类方法的示例包括 Select
、Where
、OrderBy
和 GroupBy
。
注意客户端评估
使用 System.Linq.Async
包时,请注意在客户端上执行的 LINQ 操作。 下面的查询将提取所有项,只对它们进行计数:
// ⚠️ DON'T DO THIS! 😲
int expensiveSecretCount =
await client.GetPropertiesOfSecretsAsync()
.CountAsync();
警告
同样的警告适用于 Where
等运算符。 如可用,始终首选服务器端的数据筛选、聚合或投影。
作为可观察序列
System.Linq.Async
包主要用于针对 IAsyncEnumerable<T>
序列提供观察者模式功能。 异步流基于拉取。 循环访问它们的项时,下一个可用项已拉取。 此方法与基于推送的观察者模式并列。 当项可用时,会将它们推送给充当观察者的订阅者。 System.Linq.Async
包提供了 ToObservable
扩展方法,由此可将 IAsyncEnumerable<T>
转换为 IObservable<T>
。
假设 IObserver<SecretProperties>
实现:
sealed file class SecretPropertyObserver : IObserver<SecretProperties>
{
public void OnCompleted() =>
Console.WriteLine("Done observing secrets");
public void OnError(Exception error) =>
Console.WriteLine($"Error observing secrets: {error}");
public void OnNext(SecretProperties secret) =>
Console.WriteLine($"Observable: {secret.Name}");
}
可按如下方式使用 ToObservable
扩展方法:
IDisposable UseTheToObservableMethod()
{
AsyncPageable<SecretProperties> allSecrets =
client.GetPropertiesOfSecretsAsync();
IObservable<SecretProperties> observable = allSecrets.ToObservable();
return observable.Subscribe(
new SecretPropertyObserver());
}
在前述 C# 代码中:
- 调用了 SecretClient.GetPropertiesOfSecretsAsync 方法并返回了一个
AsyncPageable<SecretProperties>
对象。 - 对
AsyncPageable<SecretProperties>
实例调用了ToObservable()
方法,返回了IObservable<SecretProperties>
。 - 订阅了
observable
,传入观察者实现,并将订阅返回给调用方。 - 订阅是
IDisposable
。 释放时,订阅终止。
循环访问可分页项
Pageable<T>
是 AsyncPageable<T>
的同步版本,可与正常 foreach
循环一起使用。
void IterateWithPageable()
{
Pageable<SecretProperties> allSecrets = client.GetPropertiesOfSecrets();
foreach (SecretProperties secret in allSecrets)
{
Console.WriteLine($"IterateWithPageable: {secret.Name}");
}
}
重要
虽然此同步 API 可用,但为了获得更好的体验,请使用异步 API 替代项。