Apa yang istimewa tentang IAsyncEnumerable di .NET Core 3.0?

Terjemahan artikel disiapkan untuk mengantisipasi dimulainya kursus "C # Developer" .










Salah satu fitur terpenting dari .NET Core 3.0 dan C # 8.0 adalah fitur baru IAsyncEnumerable<T>(alias utas asinkron). Tapi apa istimewanya dia? Apa yang bisa kita lakukan sekarang yang sebelumnya tidak mungkin?



Dalam artikel ini kita akan melihat tugas apa yang IAsyncEnumerable<T>ingin diselesaikan, bagaimana menerapkannya dalam aplikasi kita sendiri, dan mengapa itu IAsyncEnumerable<T>akan menggantikannya dalam banyak situasi. Lihat semua fitur baru di .NET Core 3Task<IEnumerable<T>>







Kehidupan sebelumnya IAsyncEnumerable<T>



Mungkin cara terbaik untuk menjelaskan mengapa IAsyncEnumerable<T>ini sangat berguna adalah dengan melihat masalah yang kita temui sebelumnya.



Bayangkan kita membuat perpustakaan untuk berinteraksi dengan data, dan kita membutuhkan metode yang meminta beberapa data dari penyimpanan atau API. Biasanya metode ini mengembalikan seperti ini:Task<IEnumerable<T>>



public async Task<IEnumerable<Product>> GetAllProducts()


Untuk mengimplementasikan metode ini, kami biasanya meminta data secara asynchronous dan mengembalikannya setelah selesai. Masalah dengan ini menjadi lebih jelas ketika kita perlu melakukan beberapa panggilan asinkron untuk mendapatkan data. Misalnya, database atau API kami dapat mengembalikan data di seluruh halaman, seperti implementasi ini menggunakan 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;
}


Perhatikan bahwa kita mengulang semua hasil dalam while loop, membuat instance objek produk, memasukkannya ke dalam List, dan akhirnya mengembalikan semuanya. Ini cukup tidak efisien, terutama pada kumpulan data yang besar.



Mungkin kita bisa membuat implementasi yang lebih efisien dengan memodifikasi metode kita sehingga mengembalikan hasil seluruh halaman pada satu waktu:



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;
        });
    }
}


Penelepon akan menggunakan metode seperti ini:



foreach (var productsTask in productsRepository.GetAllProducts())
{
    foreach (var product in await productsTask)
    {
        Console.WriteLine(product.Name);
    }
}


Implementasi ini lebih efisien, tetapi metodenya sekarang kembali . Seperti yang dapat kita lihat dari kode panggilan, memanggil metode dan memproses data tidak intuitif. Lebih penting lagi, paging adalah detail implementasi metode akses data yang tidak perlu diketahui pemanggil.IEnumerable<Task<IEnumerable<Product>>>



IAsyncEnumerable<T> bergegas untuk membantu



Apa yang benar-benar ingin kami lakukan adalah mengambil data dari database kami secara asinkron dan meneruskan hasilnya kembali ke pemanggil saat diterima.



Dalam kode sinkron, metode yang mengembalikan IEnumerable dapat menggunakan pernyataan pengembalian hasil untuk mengembalikan setiap bagian data ke pemanggil saat berasal dari database.



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;
        }
    }
}


Namun, JANGAN PERNAH LAKUKAN INI ! Kode di atas mengubah panggilan database asinkron menjadi panggilan pemblokiran dan tidak berskala.



Kalau saja kita bisa menggunakan yield returndengan metode async! Itu tidak mungkin ... sampai sekarang.



IAsyncEnumerable<T>diperkenalkan di .NET Core 3 (.NET Standard 2.1). Ini menyediakan enumerator yang memiliki metode MoveNextAsync()yang mungkin diharapkan. Ini berarti bahwa pemrakarsa dapat melakukan panggilan asynchronous sementara (di tengah) menerima hasil.



Alih-alih mengembalikan Task <IEnumerable <T >>, metode kami sekarang dapat mengembalikan IAsyncEnumerable<T>dan menggunakan data hasil pengembalian.



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;
        }
    }
}


Untuk menggunakan hasil, kita perlu menggunakan sintaks baru yang await foreach()tersedia di C # 8:



await foreach (var product in productsRepository.GetAllProducts())
{
    Console.WriteLine(product);
}


Ini jauh lebih bagus. Metode ini menghasilkan data saat masuk. Kode panggilan menggunakan data dengan kecepatannya sendiri.



IAsyncEnumerable<T> dan ASP.NET Core



Dimulai dengan .NET Core 3 Preview 7 , ASP.NET dapat mengembalikan IAsyncEnumerable dari tindakan pengontrol API. Ini berarti bahwa kita dapat mengembalikan hasil metode kita secara langsung - secara efektif meneruskan data dari database ke dalam respons HTTP.



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


Pengganti untukTask<IEnumerable<T>>IAsyncEnumerable<T>



Seiring berjalannya waktu ketika kita menjadi lebih akrab dengan .NET Core 3 dan .NET Standard 2.1, ini diharapkan IAsyncEnumerable<T>dapat digunakan di tempat-tempat di mana kita biasanya menggunakan Task <IEnumerable>.



Saya menantikan dukungan IAsyncEnumerable<T>di perpustakaan. Di artikel ini, kami melihat kode serupa untuk membuat kueri data menggunakan 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);
    }
}


Seperti pada contoh kami sebelumnya, Cosmos DB SDK asli juga memuat kami dengan detail implementasi paging, sehingga menyulitkan untuk memproses hasil kueri.



Untuk melihat seperti apa tampilannya jika GetItemQueryIterator<Product>()dikembalikan IAsyncEnumerable<T>, kita dapat membuat metode ekstensi di 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;
            }
        }
    }
}


Kami sekarang dapat menangani hasil kueri kami dengan cara yang jauh lebih baik:



var products = container
    .GetItemQueryIterator<Product>("SELECT * FROM c")
    .ToAsyncEnumerable();
await foreach (var product in products)
{
    Console.WriteLine(product.Name);
}


Ringkasan



IAsyncEnumerable<T>- adalah tambahan yang disambut baik untuk .NET dan dalam banyak kasus akan membuat kode Anda lebih menyenangkan dan efisien. Anda dapat mengetahui lebih lanjut tentang ini di sumber daya ini:








Sebutkan pola desain






Baca lebih banyak:






All Articles