System.Threading.Channels
ابنوا مسارات المنتج-المستهلك باستخدام Channel وChannelWriter وChannelReader للتحكم في الضغط العكسي.
System.Threading.Channels درس مجاني في C# Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C# Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C# Academy 4 دروس في المجموع.
ما القنوات؟
توفر System.Threading.Channels (المُقدَّمة في .NET Core 3) قائمة انتظار عالية الأداء وآمنة للخيوط بنمط المنتج-المستهلك. وعلى خلاف BlockingCollection، تعمل القنوات بشكل غير متزامن بالكامل وتتميز بكفاءة تخصيص الذاكرة — مما يجعلها مثالية لخطوط المعالجة داخل العملية.
إنشاء قناة
تُنشأ القنوات باستخدام مصنع. اختر قناة غير محدودة (دون حد) أو محدودة (بسعة محدودة مع ضغط عكسي). يعيد المصنع Channel<T> مع Writer وReader.
using System.Threading.Channels;
// Unbounded: unlimited capacity, no backpressure
var unbounded = Channel.CreateUnbounded<string>();
// Bounded: max 100 items; writer waits when full
var bounded = Channel.CreateBounded<string>(100);
// Bounded with drop-oldest strategy:
var dropping = Channel.CreateBounded<string>(new BoundedChannelOptions(50)
{
FullMode = BoundedChannelFullMode.DropOldest
});الكتابة إلى قناة
يكتب المنتجون العناصر باستخدام WriteAsync (ينتظر إذا كانت القناة المحدودة ممتلئة) أو TryWrite (يعيد false فورًا إذا كانت ممتلئة). أشر إلى الاكتمال باستخدام Writer.Complete().
var channel = Channel.CreateUnbounded<int>();
// Producer task
var producer = Task.Run(async () =>
{
for (int i = 0; i < 100; i++)
{
await channel.Writer.WriteAsync(i);
await Task.Delay(10);
}
channel.Writer.Complete(); // signal no more items
});القراءة من قناة
يقرأ المستهلكون باستخدام ReadAllAsync() (وهو الأسلوب الأبسط) أو ReadAsync/TryRead لمزيد من التحكم. تكتمل ReadAllAsync عندما يستدعي الكاتب Complete().
// Consumer task
var consumer = Task.Run(async () =>
{
await foreach (var item in channel.Reader.ReadAllAsync())
{
Console.WriteLine($"Processed: {item}");
// Naturally paced — waits for next item
}
Console.WriteLine("Channel completed");
});
await Task.WhenAll(producer, consumer);منتجون متعددون
القنوات آمنة للخيوط. يمكن لمنتجين متعددين الكتابة بالتزامن — ولا حاجة إلى أقفال. استدعِ Writer.Complete() بعد انتهاء جميع المنتجين فقط.
var channel = Channel.CreateUnbounded<WorkItem>();
var producers = Enumerable.Range(0, 4).Select(id =>
Task.Run(async () =>
{
for (int i = 0; i < 25; i++)
await channel.Writer.WriteAsync(new WorkItem(id, i));
}));
// Wait for all producers before completing the writer
await Task.WhenAll(producers);
channel.Writer.Complete();مستهلكون متعددون (التوزيع)
شغّل مهام مستهلكة متعددة على قارئ القناة نفسه لتنفيذ المعالجة بالتوازي. يُسلَّم كل عنصر إلى مستهلك واحد فقط (توزيع، وليس بثًا).
var channel = Channel.CreateBounded<WorkItem>(100);
// 4 parallel consumers
var consumers = Enumerable.Range(0, 4).Select(id =>
Task.Run(async () =>
{
await foreach (var item in channel.Reader.ReadAllAsync())
{
await ProcessItemAsync(item);
Console.WriteLine($"Consumer {id} processed {item.Id}");
}
}));
await Task.WhenAll(consumers);نمط خط المعالجة
اربط القنوات معًا في خط معالجة: تقرأ كل مرحلة من قناة، وتعالج العناصر، ثم تكتب إلى القناة التالية. تعمل المراحل بالتزامن مع ضغط عكسي طبيعي.
// Stage 1: raw data
var stage1 = Channel.CreateBounded<string>(50);
// Stage 2: parsed
var stage2 = Channel.CreateBounded<ParsedRecord>(50);
// Stage 3: enriched
var stage3 = Channel.CreateBounded<EnrichedRecord>(50);
var parse = ParseStageAsync(stage1.Reader, stage2.Writer);
var enrich = EnrichStageAsync(stage2.Reader, stage3.Writer);
var persist = PersistStageAsync(stage3.Reader);
await Task.WhenAll(parse, enrich, persist);الضغط العكسي باستخدام القنوات المحدودة
تطبّق القناة المحدودة ضغطًا عكسيًا تلقائيًا: عندما تمتلئ القناة، يعلّق WriteAsync المنتج حتى يسحب المستهلك العناصر. ولا حاجة إلى كتابة كود يدوي للتحكم في المعدل.
// Bounded channel: max 10 items
var channel = Channel.CreateBounded<string>(10);
// Fast producer
var producer = Task.Run(async () =>
{
for (int i = 0; i < 1000; i++)
{
// WriteAsync waits when channel has 10 items
await channel.Writer.WriteAsync($"item-{i}");
// Producer is naturally slowed to consumer speed
}
channel.Writer.Complete();
});
// Slow consumer
var consumer = Task.Run(async () =>
{
await foreach (var item in channel.Reader.ReadAllAsync())
{
await Task.Delay(50); // simulate slow processing
Console.WriteLine(item);
}
});معالجة الأخطاء في القنوات
مرّر استثناءً إلى Writer.Complete(exception) لنشر الأخطاء إلى جميع القرّاء المنتظرين. ويرى القرّاء الاستثناء عند محاولة القراءة التالية من القناة.
var channel = Channel.CreateUnbounded<int>();
var producer = Task.Run(async () =>
{
try
{
for (int i = 0; i < 100; i++)
{
if (i == 50) throw new Exception("Producer failed at 50");
await channel.Writer.WriteAsync(i);
}
channel.Writer.Complete();
}
catch (Exception ex)
{
channel.Writer.Complete(ex); // propagate to reader
}
});
try
{
await foreach (var item in channel.Reader.ReadAllAsync())
Console.WriteLine(item);
}
catch (Exception ex)
{
Console.Error.WriteLine($"Channel error: {ex.Message}");
}من الواقع العملي: قائمة انتظار للمهام الخلفية
قائمة انتظار للمهام الخلفية تستخدم خدمة مستضافة وقناة: تضع طلبات HTTP المهام في قائمة الانتظار، ثم يعالجها عامل خلفي واحدة تلو الأخرى.
public class JobQueue
{
private readonly Channel<Func<CancellationToken, Task>> _queue
= Channel.CreateBounded<Func<CancellationToken, Task>>(100);
public ChannelWriter<Func<CancellationToken, Task>> Writer => _queue.Writer;
public ChannelReader<Func<CancellationToken, Task>> Reader => _queue.Reader;
}
public class JobProcessor : BackgroundService
{
private readonly JobQueue _queue;
public JobProcessor(JobQueue q) => _queue = q;
protected override async Task ExecuteAsync(CancellationToken ct)
{
await foreach (var job in _queue.Reader.ReadAllAsync(ct))
await job(ct);
}
}اختبار سريع
ماذا يحدث عندما يستدعي منتج WriteAsync على قناة محدودة ممتلئة؟
مراجعة: System.Threading.Channels
أهم النقاط المستخلصة:
- توفر القنوات قائمة انتظار عالية الأداء وبنمط غير متزامن أصلي للمنتج-المستهلك
- غير المحدودة: دون حد؛ المحدودة: سعة محدودة مع ضغط عكسي أو استراتيجيات إسقاط
- المنتجون والمستهلكون المتعددون آمنون دون استخدام الأقفال
ReadAllAsync()+await foreach= أنظف نمط للاستهلاك- اربط القنوات في خطوط معالجة لتنفيذ متزامن قائم على المراحل
- استدعِ
Writer.Complete()(أوComplete(exception)) للإشارة إلى نهاية التدفق
الأسئلة الشائعة
هل درس «System.Threading.Channels» مجاني؟
نعم — نص درس «System.Threading.Channels» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C# Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C# Academy 4 دروس في المجموع.
ماذا ستتعلم في «System.Threading.Channels»؟
ابنوا مسارات المنتج-المستهلك باستخدام Channel وChannelWriter وChannelReader للتحكم في الضغط العكسي. تتمرن على C# Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ C# Academy؟
لا تُشترط خبرة سابقة. C# Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.
كم من الوقت يستغرق درس «System.Threading.Channels»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس C# Academy هذا؟
نعم. كل درس في C# Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- IAsyncEnumerable وawait foreach
- System.Threading.Channels
- ValueTask وتجنّب التخصيصات
- ConfigureAwait وسياق المزامنة