.NET Core 3.0のIAsyncEnumerableの何が特別なのですか?

記事の翻訳は、「C#Developer」コースの開始を見越して作成されました










.NET Core 3.0およびC#8.0の最も重要な機能の1つは、新しい機能IAsyncEnumerable<T>(別名非同期スレッド)です。しかし、彼の何がそんなに特別なのですか?以前は不可能だった今、私たちは何ができるでしょうか?



この記事ではIAsyncEnumerable<T>、解決することを目的としたタスク、独自のアプリケーションに実装する方法、および多くの状況でIAsyncEnumerable<T>置き換えられる理由について説明します.NET Core3の すべての新機能を確認してください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ループでループし、製品オブジェクトをインスタンス化し、それらをリストに入れて、最後にすべてを返すことに注意してください。これは、特に大規模なデータセットでは非常に非効率的です。



おそらく、一度にページ全体の結果を返すようにメソッドを変更することで、より効率的な実装を作成できます。



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()予想される可能性のあるメソッド持つ列挙子を提供しますこれは、イニシエーターが結果の受信中(途中)に非同期呼び出しを行うことができることを意味します。



Task <IEnumerable <T >>を返す代わりに、メソッドが戻り、yieldreturnIAsyncEnumerable<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コア



以降では、.NETのコア3のプレビュー7、ASP.NETは、APIのコントローラのアクションからIAsyncEnumerableを返すことができます。これは、メソッドの結果を直接返すことができることを意味します。つまり、データベースからHTTP応答にデータを効果的に渡します。



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


交換のためにTask<IEnumerable<T>>IAsyncEnumerable<T>



.NET Core3と.NETStandard 2.1に慣れてくると、IAsyncEnumerable<T>通常は<TaskIEnumerable>を使用していた場所で使用されることが予想されます図書館での



サポートを楽しみにしてIAsyncEnumerable<T>います。この記事では、Azure Cosmos DB 3.0SDKを使用してデータをクエリするための同様のコードを見ました。



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