Azure SDK for .NET での改ページ

この記事では、Azure SDK for .NET の改ページ機能を使用して、大規模なデータ セットを効率的かつ生産的に操作する方法について説明します。 改ページは、大きなデータ セットをページに分割して、コンシューマーがより少量のデータを反復処理しやすくする操作です。 C# 8 以降では、非同期ストリームを使用してストリームを非同期で作成および使用できます。 非同期ストリームは、IAsyncEnumerable<T> インターフェイスに基づいています。 Azure SDK for .NET により、その AsyncPageable<T> クラスを使用して IAsyncEnumerable<T> の実装が公開されます。

この記事のすべてのサンプルは、次の NuGet パッケージに依存しています。

Azure SDK for .NET パッケージの最新のディレクトリについては、Azure SDK の最新リリースに関する記事を参照してください。

改ページ可能な戻り値の型

Azure SDK for .NET からインスタンス化されたクライアントは、次のページング可能な型を返すことができます。

Type 説明
Pageable<T> ページで取得された値のコレクション
AsyncPageable<T> ページで非同期で取得された値のコレクション

この記事のほとんどのサンプルは、AsyncPageable<T> 型のバリエーションを使用した非同期のものです。 I/O バインド操作には非同期プログラミングを使用するのが理想的です。 特に最適な使用例は Azure SDK for .NET で非同期 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# コードでは:

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.AsyncAsyncPageable と共に使用する

System.Linq.Async パッケージは、IAsyncEnumerable<T> 型で動作する LINQ メソッドのセットを提供します。 AsyncPageable<T> では IAsyncEnumerable<T> が実装されているため、System.Linq.Async を使用してデータのクエリと変換を行うことができます。

List<T> に変換する

AsyncPageable<T>List<T> に変換するには、ToListAsync を使用します。 このメソッドにより、1 つのページでデータが返されない場合、複数のサービス呼び出しが実行される可能性があります。

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> オブジェクトが返されます。
  • 新しい List<SecretProperties> インスタンスを具体化する ToListAsync メソッドが待機されます。

最初の 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 と同等の機能を提供する、他のメソッドが用意されています。 このようなメソッドの例としては、SelectWhereOrderByGroupBy などがあります。

クライアント側の評価に注意する

System.Linq.Async パッケージを使用する場合は、LINQ 操作がクライアントで実行されることに注意してください。 次のクエリでは、それらをカウントするために、"すべての" 項目がフェッチされます。

// ⚠️ DON'T DO THIS! 😲
int expensiveSecretCount =
    await client.GetPropertiesOfSecretsAsync()
        .CountAsync();

警告

Where のような演算子にも同じ警告が適用されます。 可能な限り、サーバー側のデータのフィルター処理、集計、プロジェクションを常に優先してください。

監視可能なシーケンスとして

System.Linq.Async パッケージは、主に IAsyncEnumerable<T> シーケンスに対してオブザーバー パターン機能を提供するために使用されます。 非同期ストリームはプルベースです。 項目が反復処理されている間に、次に使用可能な項目が "プル" されます。 これは、プッシュ ベースのオブザーバー パターンとは対照的な方法です。 項目が使用可能になると、オブザーバーとして機能するサブスクライバーに "プッシュ" されます。 System.Linq.Async パッケージでは、IAsyncEnumerable<T>IObservable<T> に変換できるようにする ToObservable 拡張メソッドが提供されています。

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> オブジェクトが返されます。
  • ToObservable() メソッドは、AsyncPageable<SecretProperties> インスタンスで呼び出され、IObservable<SecretProperties> を返します。
  • observable がサブスクライブされ、オブザーバーの実装で渡され、呼び出し元にサブスクリプションを返します。
  • サブスクリプションは IDisposable です。 それが破棄されると、サブスクリプションは終了します。

改ページ可能なものを反復処理する

Pageable<T> は、通常の foreach ループで使用できる AsyncPageable<T> の同期バージョンです。

void IterateWithPageable()
{
    Pageable<SecretProperties> allSecrets = client.GetPropertiesOfSecrets();

    foreach (SecretProperties secret in allSecrets)
    {
        Console.WriteLine($"IterateWithPageable: {secret.Name}");
    }
}

重要

この同期 API は使用可能ですが、エクスペリエンスを向上するために、非同期 API の代替手段を使用してください。

関連項目