الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

تركيب البيانات BlockingCollection (جديد دوت نت 4)

بدأه عبد العظيم بخاري في 7 نوفمبر 2010 · 1 رد · 641 مشاهدة · في Microsoft Visual C#.NET
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

سوف اتحدث اليوم عن تركيب بيانات جديد مثير رائع في اطار عمل الدوت نت 4 وهو BlockingCollection<T> الذي هو عبارة عن تركيب thread-safe للبيانات وتم تسميته بBlockingCollection نظراً لاسباب التالية:

- في حال كانت المجموعة فارغة من العناصر وكان هناك كود ما يريد اخذ عنصر من هذه المجموعة فالthread التي تنفذ هذا الكود سيتم عمل block لها طالما لم يتم اضافة أي عنصر .

- في حال كانت المجموعة ممتلئة بالحد الأقصى من العناصر وكان عندنا thread يحاول اضافة عنصر جديد فسيتم عمل block له حتى يتم توفير مكان فارغ لاضافة العنصر الجديد.

طبعاً هذا ليس وصف كامل لامكانيات هذه المجموعة . وقبل التعرف عليها سنرى اولاً بعض الmethods المهمة بها من اجل التعامل مع البيانات :

الBlocking methods :

Add :

Add(T); 
Add(T, CancellationToken);

Take:

Take() 
Take(CancellationToken)

الnon-Blocking methods:

Add :

TryAdd(T) 
TryAdd(T, Int32) 
TryAdd(T, TimeSpan) 
TryAdd(T, Int32, CancellationToken)

Take:

TryTake(out T) 
TryTake(out T, Int32) 
TryTake(out T, TimeSpan) 
TryTake(out T, Int32, CancellationToken)

لاحظ أننا نمتلك كلا النوعين من الmethods والتي هي blocking و non-blocking (التي تبدأ بكلمة Try) .

يرجع نوع الnon-blocking قيمة true عند النجاح باضافة عنصر أو اخذه من المجموعة ويرجع قيمة false في حالة الفشل .

لاحظ ايضا أن كلا النوعين يمتلكان overloads مع CancellationToken والتي تسمح بالغاء الميثود بشكل async .

تمتلك مجموعة BlockingCollection<T> الية مخصصة للإكمال حيث أن المجموعة تكون مكتملة في حال كنا لن نضيف له أي بيانات اخرى في المستقبل . وعند الوصول لحالة الإكتمال فأي محاولة لاضافة أي بيانات لهذه المجموعة فسوف تعطينا الexception الذي اسمه InvalidOperationException .

ونفس الأمر يحصل عندما نحاول اخذ عنصر من مجموعة فارغة .

يمكن للمجموعة أن تصل لحالة الإكتمال بطريقتين ; فإما أن تمتلئ بالعناصر أو يتم استدعاء الميثود CompleteAdding() لها .

يوضح المثال التالي كيفية العمل مع مجموعة BlockingCollection<T> . وفي السطر الاول سنقوم بتهيئة المجموعة ووضع الحد الأعلى من العناصر لها مع العلم انه يمكننا استعمال الconstructor الذي ليس له أي باراميتر وذلك للحصول على حد غير نهائي لهذه المجموعة :

نضع اولاً في اعلى الصفحة

using System.Collections.Concurrent;
using System.Threading;
...

BlockingCollection<int> myCollection = new BlockingCollection<int>(20);

بعدها سنقوم باضافة بعض العناصر لهذه المجموعة وطباعة بعض الخصائص التي سنتخدث عنها لاحقاً :

myCollection.Add(3000);
myCollection.Add(4000);
myCollection.Add(5000);
myCollection.Add(6000);
myCollection.Add(7000);
myCollection.Add(8000);

Console.WriteLine();
Console.WriteLine("Count: {0}" , myCollection.Count);
Console.WriteLine("BoundedCapacity: {0}" , myCollection.BoundedCapacity);
Console.WriteLine("IsCompleted: {0}" , myCollection.IsCompleted);
Console.WriteLine("IsAddingCompleted: {0}", myCollection.IsAddingCompleted);
Console.WriteLine();

بعد ذلك سنقوم بتشغيل timer يقوم باضافة عناصر لهذه المجموعة بعد 5 ثواني عن طريق thread منفصلة وبعدها يقوم بجعل هذه المجموعة مكتملة عن طريق الميثود CompleteAdding()

Timer timer = new Timer(delegate{ 
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine(DateTime.Now.ToLongTimeString() + " Adding the element :" + " 9000");
myCollection.Add(9000);
Thread.SpinWait(500000000);
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine(DateTime.Now.ToLongTimeString() + " Adding the element " + " 10000");
myCollection.Add(10000);  
myCollection.CompleteAdding();
}, null, 5000, Timeout.Infinite);

وكما قلنا قبل قليل فهذا الtimer سيتم تنفيذه بعد 5 ثواني في thread منفصلة عن الthread الرئيسية للبرنامج والتي تستمر بتنفيذ ميثود Main() مما يسمح لنا باجراء عملية سحب للعناصر في نفس وقت اضافة العناصر (كل thread ستتولى عملية منهما).

نكمل بعدها كود الMain() باضافة كود يقوم بسحب العناصر من المجموعة :

foreach (var element in myCollection.GetConsumingEnumerable())
{ 
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(DateTime.Now.ToLongTimeString() + " Taking the element " + element);
}
Console.WriteLine("IsCompleted: {0}" , myCollection.IsCompleted);
Console.WriteLine("IsAddingCompleted: {0}", myCollection.IsAddingCompleted);
Console.ReadLine();

ناتح المثال:

38929984.png

الذي فمنا به في هذا المثال هو أننا اضفنا 6 عناصر للمجموعة وبعدنا بدأنا الtimer فاصبح عندنا اثنتين من الthreads:

- الthread الرئيسية التي تنفذ Main() والتي تأخذ العناصر من المجموعة .

- الthread الفرعية للtimer والتي تبدأ بعد 5 ثوان وتبدأ باضافة عناصر اخرى للمجموعة .

لو نظرنا لصورة النتائج فسنرى أن العناصر ال6 الاولى تم اضافتهم في زمن يقدر باقل من ثانية واحدة وتابعنا تنفيذ الthread الرئيسية حتى قمنا بسحب هذه العناصر ال6 وبعد أن يجد الكود الذي يسحب العناصر بانه لم يعد هناك عناصر اخرى فسيتم عمل بلوك للthread الرئيسية لميثود Main() بانتظار وصول عناصر اخرى لهذه المجموعة وهذا ما يقوم به الtimer بعد مضي 5 ثوان حيث يقوم باضافة العنصر 9000 ومباشرة يتم عمل unblock للthread الرئيسية لتسحب هذا العنصر مباشرة وبعدها تصبح المجموعة فارغة فيتم عمل block مرة اخرى للthread الرئيسية وبعد ذلك يقوم الtimer بتنفيذ SpinWait() ليعمل بلوك له ل5 ثوان اخرى وبعدها يضيف العنصر 10000 ويتم عمل unblock للthread الرئيسية لتسحب هذا العنصر وفي النهاية يقوم الtimer باستدعاء الميثود CompleteAdding() للمجموعة ليرسل اشعار للthread الرئيسية حتى تعمل unblock وتنتهي من عملية السحب للعناصر لأن بهذه الميثود تكون المجموعة اكتملت وجميع العناصر تمت معالجتها .

اما اذا ازلنا استدعاء الميثود CompleteAdding() فسيستمر بلوك الthread الرئيسية للابد حيث انها ستستمر بانتظار قدوم عناصر اخرى للمجموعة إلى مالانهاية لذلك استعمال الميثود CompleteAdding() يمنعنا من الوصول لهذه الحالة .

بعض الproperties المفيدة لهذه المجموعة :

- IsAddingCompleted : ترجع true في حال تم التأشير على المجموعة بأنها اكتملت , و عدا ذلك ترجع false .

- IsCompleted : ترجع true في حال كانت المجموعة مكتملة وفارغة . وعدا ذلك ترجع false .

- BoundedCapacity : ترجع السعة القصوى للمجموعة , وترجع -1 في حال كانت المجموعة متمددة أو غير محددة السعة .

لو نظرنا للمثال السابق فسنلاحظ أن في حالة احتواء المجموعة على بعض العناصر فإن كلتا الproperties سيكون بهما قيمة false وذلك لأن المجموعة غير مؤشر عليها بالإكتمال . اما في اللحظة الاخيرة للبرنامج فستصبحان true لأانه تم التأشير على المجموعة بأنها اكتملت وتم اخذ جميع العناصر منها .

لكن ماذا عن ترتيب العناصر في المجموعة ؟

كما لاحظنا في المثال السابق فإن العناصر ترتب على مبدأ queue FIFO . لكن هذه الحالة معرفة سابقاً وهل نستطيع التحكم بها ؟

مفتاح الجواب على هذه الأسئلة موجود في الconstructor الخاص بهذه المجموعة حيث يحتوي على باراميتر IProducerConsumerCollection<T> حيث يمكنك اعطاء نظام تخزيني لهذه العناصر في هذه المجموعة .

فيمكنك استعمال احدى المجموعات التالية :

ConcurrentStack<T> : وهي thread-safe stack

ConcurrentQueue<T> : وهي thread-safe queue

ConcurrentBag<T> : وهي مجموعة thread-safe للبيانات وهي غير مرتبة وتسمح بالتكرار .

لذلك في واقع الأمر فالBlockingCollection<T> ليست مستودع لتخزين البيانات بل هي بالتحديد الية للوصول لعناصر تلك المجموعات السابقة وتطبيق مبادئ الإكتمال عليها . وكما رأينا منذ قليل استعمال FIFO في المثال فدعنا نستعمل الان تركيب البيانات stack والذي هو LIFO :

BlockingCollection<int> myCollection = new BlockingCollection<int>(new ConcurrentStack<int>(), 20);

ونكمل بقية المثال السابق فيكون عندنا الناتج كالاتي :

123kt.png

لاحظ كيف تم عرض العناصر وسحبها من المجموعة حسب مبدأ LIFO .

يمكنك عمل مستودع بيانات خاص بك وتطبيقه على مجموعة BlockingCollection<T> عن طريق عمل implement للانترفيس التالي:

public interface IProducerConsumerCollection<T> : IEnumerable<T>, ICollection, IEnumerable{
  void CopyTo(T[] array, int index);
  T[] ToArray();
  bool TryAdd(T item);
  bool TryTake(out T item);
}

اريد أن اتكلم الان عن الmethods المثيرة لكلاس BlockingCollection<T> والتي هي من نوع static . فيمكنك التعامل مع عدد من المجموعات في ان واحد مع هذه الmethods :

Blocking
int BlockingCollection<T>.AddToAny(BlockingCollection<T>[], T);
int BlockingCollection<T>.TakeFromAny(BlockingCollection<T>[], out T);

Non-Blocking
int BlockingCollection<T>.TryAddToAny(BlockingCollection<T>[], T);
int BlockingCollection<T>.TryTakeFromAny(BlockingCollection<T>[], out T);

تأخذ جميع هذه الmethods بارميتر يمثل array من مجموعات BlockingCollection<T> وعندما نطبق هذه الmethods فسوف تعمل هذه المجموعات كما شرحنا منذ قليل الميثود Add() والميثود Take() فمثلاً تقوم هذه الmethods بارجاع اندكس للمجموعة التي تم اضافة البيانات اليها أو اخذها أو ترجع -1 في حال كانت المحاولة فاشلة .

كل هذه التفاصيل تجدها على موقع MSDN على الرابط التالي :

http://msdn.microsoft.com/en-us/library/dd287223%28VS.100%29.aspx

يمكنك الحصول على امثلة اخرى رائعة للBlockingCollection<T> من هنا :

http://code.msdn.microsoft.com/ParExtSamples

عبد العظيم بخاري

http://www.el-bukhari.com/2010/11/blockingcollection-4.html

1
#2

الف الف شكر على هذا الشرح الرائع

تم تعديل هذه المشاركة بواسطة mesho100200 في 11 مارس 2014 في 00:15

مواضيع مشابهة