本文介绍如何使用用于 .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 实例化的客户端可以返回以下可分页类型。

    本文中的大多数示例都是异步的,使用了 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(); 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 替代项。

  • 用于 .NET 的 Azure SDK 的依赖关系注入
  • Azure SDK 对象的线程安全性和客户端生存期管理
  • System.Linq.Async
  • 基于任务的异步模式
  •