0Pricing
C# Academy · درس

IAsyncEnumerable وawait foreach

ابثّوا البيانات بصورة غير متزامنة باستخدام IAsyncEnumerable، واستخدموا yield في المكرّرات غير المتزامنة، واستهلكوها باستخدام await foreach.

IAsyncEnumerable وawait foreach درس مجاني في C# Academy على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C# Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C# Academy 4 دروس في المجموع.

لماذا نستخدم IAsyncEnumerable؟

يتيح IAsyncEnumerable<T> (المُقدَّم في C# 8 و.NET Core 3) التدفق غير المتزامن — إذ تُنتج العناصر واحدًا تلو الآخر عند توفرها، دون تحميل كل شيء في الذاكرة أولًا. فكّر في قراءة صفوف مؤشر قاعدة البيانات صفًا تلو الآخر، أو في تدفق استجابات API.

كتابة مكرّر غير متزامن

صرّح عن أسلوب يعيد IAsyncEnumerable<T>، واستخدم yield return داخل أسلوب غير متزامن. تتعايش الكلمتان المفتاحيتان await وyield return بشكل طبيعي.

public async IAsyncEnumerable<int> GenerateNumbersAsync(int count)
{
    for (int i = 0; i < count; i++)
    {
        await Task.Delay(100); // simulate async work per item
        yield return i;
    }
}

الاستهلاك باستخدام await foreach

استهلك IAsyncEnumerable<T> باستخدام await foreach. ينتظر كل تكرار العنصر التالي بشكل غير متزامن — وبذلك تتناغم سرعة المستهلك والمنتج طبيعيًا.

await foreach (var number in GenerateNumbersAsync(10))
{
    Console.WriteLine(number);
}

// With cancellation:
var cts = new CancellationTokenSource();
await foreach (var n in GenerateNumbersAsync(100)
    .WithCancellation(cts.Token))
{
    if (n > 50) { cts.Cancel(); break; }
    Console.WriteLine(n);
}

التدفق من قاعدة بيانات

يدعم EF Core تدفق نتائج الاستعلام باستخدام AsAsyncEnumerable(). يقرأ هذا الصفوف واحدًا تلو الآخر دون تحميل مجموعة النتائج كاملة إلى الذاكرة — وهو أمر بالغ الأهمية للجداول الكبيرة.

public async IAsyncEnumerable<Product> StreamProductsAsync(
    [EnumeratorCancellation] CancellationToken ct = default)
{
    await foreach (var product in _db.Products
        .Where(p => p.IsActive)
        .AsAsyncEnumerable()
        .WithCancellation(ct))
    {
        yield return product;
    }
}

تدفق استجابات HTTP

ابثّ الاستجابات الكبيرة إلى العملاء دون تخزين الحمولة كاملة مؤقتًا. يتلقى العميل البيانات تدريجيًا بينما يواصل الخادم المعالجة.

app.MapGet("/stream/products", (ProductRepository repo) =>
{
    // Returns an IAsyncEnumerable — ASP.NET Core streams it as JSON array
    return repo.StreamAllAsync();
});

// Or with custom serialization:
app.MapGet("/stream/events", async (HttpResponse response, EventStore store) =>
{
    response.ContentType = "application/json";
    await foreach (var e in store.StreamAsync())
    {
        await response.WriteAsJsonAsync(e);
        await response.Body.FlushAsync();
    }
});

السمة EnumeratorCancellation

طبّق [EnumeratorCancellation] على مَعلمة CancellationToken في المكرّر غير المتزامن، لكي تمرّر WithCancellation() في جهة المستهلك الرمز المميز إلى المنتج.

public async IAsyncEnumerable<LogEntry> TailLogAsync(
    string logFile,
    [EnumeratorCancellation] CancellationToken ct = default)
{
    while (!ct.IsCancellationRequested)
    {
        var newLines = await ReadNewLinesAsync(logFile, ct);
        foreach (var line in newLines)
            yield return new LogEntry(line);
        await Task.Delay(500, ct);
    }
}

// Consumer:
await foreach (var entry in TailLogAsync("/var/log/app.log")
    .WithCancellation(cts.Token))
{
    Console.WriteLine(entry.Message);
}

استخدام LINQ مع IAsyncEnumerable

توفر حزمة NuGet المسماة System.Linq.Async عوامل LINQ غير متزامنة (WhereAwait وSelectAwait وToListAsync) لـ IAsyncEnumerable.

// dotnet add package System.Linq.Async

var expensiveProducts = await repo.StreamAllAsync()
    .WhereAwait(async p => await pricing.IsExpensiveAsync(p.Id))
    .SelectAwait(async p => await enricher.EnrichAsync(p))
    .Take(10)
    .ToListAsync();

// Or use built-in Where/Select that don't need async:
var names = repo.StreamAllAsync()
    .Where(p => p.IsActive)
    .Select(p => p.Name);

دمج تدفقات غير متزامنة متعددة

لمعالجة تدفقات غير متزامنة متعددة بالتزامن، استخدم Channel كنقطة تجميع — اكتب من عدة منتجين واقرأ من مستهلك واحد.

async IAsyncEnumerable<T> Merge<T>(
    params IAsyncEnumerable<T>[] streams)
{
    var channel = Channel.CreateUnbounded<T>();

    var producers = streams.Select(async stream =>
    {
        await foreach (var item in stream)
            await channel.Writer.WriteAsync(item);
    });

    _ = Task.WhenAll(producers)
            .ContinueWith(_ => channel.Writer.Complete());

    await foreach (var item in channel.Reader.ReadAllAsync())
        yield return item;
}

الأداء: التخزين المؤقت مقابل التدفق

استخدم التدفق دائمًا عند معالجة مجموعات بيانات كبيرة. يؤدي تحميل كل شيء أولًا إلى قائمة باستخدام ToListAsync() إلى إبقاء جميع البيانات في الذاكرة في الوقت نفسه — بينما يعالج التدفق عنصرًا واحدًا كل مرة.

// BAD: loads all 1M records into memory
var allOrders = await _db.Orders.ToListAsync();
foreach (var order in allOrders)
    await ProcessAsync(order);

// GOOD: processes one record at a time
await foreach (var order in _db.Orders.AsAsyncEnumerable())
    await ProcessAsync(order);
// Memory usage: O(1) vs O(n)

معالجة الأخطاء في المكرّرات غير المتزامنة

ضع حلقة await foreach كاملة داخل try/catch لمعالجة الأخطاء الصادرة من المنتج. تنتقل الأخطاء في المكرّر إلى المستهلك عند نقطة التكرار الحالية.

try
{
    await foreach (var item in RiskyStreamAsync())
    {
        await ProcessAsync(item);
    }
}
catch (HttpRequestException ex)
{
    Console.Error.WriteLine($"Stream error: {ex.Message}");
}
catch (OperationCanceledException)
{
    Console.WriteLine("Stream cancelled.");
}

اختبار سريع

ما الميزة الأساسية التي يوفرها IAsyncEnumerable من ناحية الذاكرة مقارنة بإعادة List<T>؟

مراجعة: IAsyncEnumerable و await foreach

أهم النقاط المستخلصة:

  • IAsyncEnumerable<T>: تدفق العناصر بشكل غير متزامن، عنصرًا واحدًا كل مرة
  • ينتج yield return في الأساليب غير المتزامنة مكرّرات غير متزامنة
  • await foreach: استهلاك التدفق مع الإلغاء باستخدام .WithCancellation()
  • [EnumeratorCancellation]: تمرير رمز المستهلك المميز إلى المكرّر
  • EF Core: استخدم AsAsyncEnumerable() للاستعلامات الكبيرة ذات الكفاءة في استخدام الذاكرة
  • عند معالجة مجموعات البيانات الكبيرة: استخدم التدفق بدلًا من ToListAsync()

الأسئلة الشائعة

هل درس «IAsyncEnumerable وawait foreach» مجاني؟

نعم — نص درس «IAsyncEnumerable وawait foreach» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C# Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C# Academy 4 دروس في المجموع.

ماذا ستتعلم في «IAsyncEnumerable وawait foreach»؟

ابثّوا البيانات بصورة غير متزامنة باستخدام IAsyncEnumerable، واستخدموا yield في المكرّرات غير المتزامنة، واستهلكوها باستخدام await foreach. تتمرن على C# Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ C# Academy؟

لا تُشترط خبرة سابقة. C# Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.

كم من الوقت يستغرق درس «IAsyncEnumerable وawait foreach»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس C# Academy هذا؟

نعم. كل درس في C# Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. IAsyncEnumerable وawait foreach
  2. System.Threading.Channels
  3. ValueTask وتجنّب التخصيصات
  4. ConfigureAwait وسياق المزامنة
← العودة إلى C# Academy