إحدى أهم ميزات .NET Core 3.0 و C # 8.0 هي الميزة الجديدة
IAsyncEnumerable<T>(المعروفة أيضًا باسم الخيط غير المتزامن). لكن ما الذي يميزه؟ ما الذي يمكننا فعله الآن والذي كان مستحيلاً من قبل؟
في هذه المقالة سنلقي نظرة على المهام
IAsyncEnumerable<T>المراد حلها ، وكيفية تنفيذها في تطبيقاتنا ، ولماذا IAsyncEnumerable<T>ستستبدلها في العديد من المواقف.
تحقق من جميع الميزات الجديدة في .NET Core 3Task<IEnumerable<T>>
الحياة من قبل IAsyncEnumerable<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> الاستعجال للمساعدة
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
IAsyncEnumerable<T>بدءًا من .NET Core 3 Preview 7 ، يمكن لـ ASP.NET إرجاع IAsyncEnumerable من إجراء وحدة تحكم API. هذا يعني أنه يمكننا إرجاع نتائج طريقتنا مباشرة - نقل البيانات بشكل فعال من قاعدة البيانات إلى استجابة HTTP.
[HttpGet]
public IAsyncEnumerable<Product> Get()
=> productsRepository.GetAllProducts();
استبدال لTask<IEnumerable<T>>IAsyncEnumerable<T>
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 وفي كثير من الحالات ستجعل شفرتك أفضل وأكثر كفاءة. يمكنك معرفة المزيد عن هذا من خلال هذه الموارد:
- البرنامج التعليمي: إنشاء واستهلاك تدفقات غير متزامنة باستخدام C # 8.0 و .NET Core 3.0
- مقترحات لغة C # - تيارات غير متزامن
نمط تصميم الدولة