قوائم المهام لـ C# (.NET): System.Threading.Channels و TPL Dataflow
عند تطوير تطبيقات متعددة الخيوط في C# (.NET)، غالبًا ما تنشأ الحاجة لتنظيم نقل البيانات غير المتزامن بين المنتجين (producers) والمستهلكين (consumers). يتم حل النمط الكلاسيكي Producer-Consumer باستخدام قوائم المهام. في بيئة .NET، هناك أداتان قويتان لذلك: System.Threading.Channels (ظهر في .NET Core 3.0) ومكتبة TPL Dataflow (جزء من .NET منذ 4.5).
في هذه المقالة، سنشرح كلا النهجين، خصائصهما، ونقدم أمثلة عملية للكود.
1. System.Threading.Channels — قائمة خفيفة وسريعة
مساحة الأسماء System.Threading.Channels توفر قوائم آمنة للخيوط وغير متزامنة، محسّنة لسيناريوهات الإنتاجية العالية. الأنواع الرئيسية:
Channel<T>— فئة مجردة للقناة.Channel.CreateUnbounded<T>()— قائمة غير محدودة (خطر تجاوز الذاكرة).Channel.CreateBounded<T>(int capacity)— قائمة محدودة مع التحكم في الضغط.ChannelWriter<T>— واجهة للكتابة.ChannelReader<T>— واجهة للقراءة.
مثال: Producer-Consumer مع Channel
لننشئ مثالًا بسيطًا، حيث يولد منتج واحد أرقامًا، ويقوم مستهلك واحد بمعالجتها.
using System.Threading.Channels;using System.Threading.Tasks;
var channel = Channel.CreateUnbounded<int>();
// Producervar producer = Task.Run(async () =>{ for (int i = 0; i < 10; i++) { await channel.Writer.WriteAsync(i); Console.WriteLine($"تم الإنتاج: {i}"); await Task.Delay(100); } channel.Writer.Complete(); // إشارة الانتهاء});
// Consumervar consumer = Task.Run(async () =>{ await foreach (var item in channel.Reader.ReadAllAsync()) { Console.WriteLine($"تمت المعالجة: {item * 2}"); await Task.Delay(200); }});
await Task.WhenAll(producer, consumer);Console.WriteLine("تم!");مزايا Channels:
- تكاليف إضافية ضئيلة (بدون تخصيص كائنات إضافية).
- دعم مدمج لـ
async/awaitوIAsyncEnumerable. - مثالي للأنظمة عالية التحميل (مثل معالجة حزم الشبكة).
2. TPL Dataflow — خط أنابيب معالجة قوي
مكتبة System.Threading.Tasks.Dataflow توفر كتلًا أكثر تعقيدًا لبناء خطوط الأنابيب (pipeline) وشبكات معالجة البيانات. الكتل الرئيسية:
BufferBlock<T>— قائمة بسيطة.ActionBlock<T>— كتلة تنفذ إجراءً على كل عنصر.TransformBlock<TInput, TOutput>— تحويل كل عنصر.BatchBlock<T>— تجميع العناصر في دفعات.
مثال: خط أنابيب مع ActionBlock
لننفذ نفس المهمة: منتج يرسل أرقامًا، ومستهلك يعالجها.
using System.Threading.Tasks.Dataflow;
var actionBlock = new ActionBlock<int>(async item =>{ await Task.Delay(200); Console.WriteLine($"تمت المعالجة: {item * 2}");}, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 2 });
// Producerfor (int i = 0; i < 10; i++){ await actionBlock.SendAsync(i); Console.WriteLine($"تم الإنتاج: {i}"); await Task.Delay(100);}
actionBlock.Complete();await actionBlock.Completion;Console.WriteLine("تم!");مزايا TPL Dataflow:
- مرونة عالية في الإعداد