ما الذي يميز IAsyncEnumerable في .NET Core 3.0؟

تم إعداد ترجمة المقال استعدادًا لبدء دورة "C # Developer" .










إحدى أهم ميزات .NET Core 3.0 و C # 8.0 هي الميزة الجديدة IAsyncEnumerable<T>(المعروفة أيضًا باسم الخيط غير المتزامن). لكن ما الذي يميزه؟ ما الذي يمكننا فعله الآن والذي كان مستحيلاً من قبل؟



في هذه المقالة سنلقي نظرة على المهام IAsyncEnumerable<T>المراد حلها ، وكيفية تنفيذها في تطبيقاتنا ، ولماذا IAsyncEnumerable<T>ستستبدلها في العديد من المواقف. تحقق من جميع الميزات الجديدة في .NET Core 3Task<IEnumerable<T>>







الحياة من قبل IAsyncEnumerable<T>



ربما تكون أفضل طريقة لشرح سبب IAsyncEnumerable<T>فائدتها هي النظر إلى المشكلات التي واجهناها من قبل.



تخيل أننا ننشئ مكتبة للتفاعل مع البيانات ، ونحتاج إلى طريقة تطلب بعض البيانات من متجر أو واجهة برمجة تطبيقات. عادةً ما تعود هذه الطريقة على النحو التالي :Task<IEnumerable<T>>



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


لتنفيذ هذه الطريقة ، نطلب عادةً البيانات بشكل غير متزامن ونعيدها عند اكتمالها. تصبح مشكلة هذا أكثر وضوحًا عندما نحتاج إلى إجراء مكالمات غير متزامنة متعددة للحصول على البيانات. على سبيل المثال ، يمكن لقاعدة البيانات أو واجهة برمجة التطبيقات الخاصة بنا إرجاع البيانات في صفحات كاملة ، مثل هذا التنفيذ باستخدام 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 استخدام بيان العائد العائد لإرجاع كل جزء من البيانات إلى المتصل لأنه يأتي من قاعدة البيانات.



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 >> ، يمكن لطريقتنا الآن إرجاع IAsyncEnumerable<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 Core



بدءًا من .NET Core 3 Preview 7 ، يمكن لـ ASP.NET إرجاع IAsyncEnumerable من إجراء وحدة تحكم API. هذا يعني أنه يمكننا إرجاع نتائج طريقتنا مباشرة - نقل البيانات بشكل فعال من قاعدة البيانات إلى استجابة HTTP.



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


استبدال لTask<IEnumerable<T>>IAsyncEnumerable<T>



مع مرور الوقت حيث أصبحنا أكثر دراية بـ .NET Core 3 و .NET Standard 2.1 ، من المتوقع أن IAsyncEnumerable<T>يتم استخدامه في الأماكن التي نستخدم فيها عادةً Task <IEnumerable>.



إنني أتطلع إلى رؤية الدعم IAsyncEnumerable<T>في المكتبات. في هذه المقالة ، رأينا رمزًا مشابهًا للاستعلام عن البيانات باستخدام 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);
    }
}


كما في الأمثلة السابقة ، فإن 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