سيناريو قائمة الانتظار
-
24-09-2019 - |
سؤال
أحتاج إلى تنفيذ قائمة انتظار منتج/مستهلك ، ومستهلكين متعددين ضد منتج واحد.
لدي وظيفة دفع تضيف عنصرًا إلى قائمة الانتظار ثم يتحقق من MaxSize. إذا وصلتنا إلى عودة كاذبة ، في كل حالة أخرى تعود صحيح.
في الكود التالي _vector هي قائمةu003CT> ، onSignal يستهلك بشكل أساسي عنصر بطريقة غير متزامنة.
هل ترى مشكلات مع هذا الرمز؟
public bool Push(T message)
{
bool canEnqueue = true;
lock (_vector)
{
_vector.Add(message);
if (_vector.Count >= _maxSize)
{
canEnqueue = false;
}
}
var onSignal = SignalEvent;
if (onSignal != null)
{
onSignal();
}
return canEnqueue;
}
المحلول
أعلم أنك قلت منتجًا واحدًا ، مستهلكًا متعدد ، لكن من الجدير بالذكر على أي حال: إذا كانت قائمة الانتظار ممتلئة تقريبًا (قل 24 من أصل 25 فتحة) ، فعندئذٍ إذا كان هناك موضوعان Push
في الوقت نفسه ، سوف ينتهي بك الأمر إلى تجاوز الحد. إذا كانت هناك فرصة حتى يكون لديك العديد من المنتجين في وقت ما في المستقبل ، فيجب عليك التفكير في صنع Push
مكالمة حظر ، وانتظر "متاح" AutoResetEvent
الذي يتم الإشارة إليه بعد إخلاء عنصر أو بعد أن يتم إلغاء عنصر بينما لا تزال هناك فتحات متاحة.
القضية المحتملة الأخرى الوحيدة التي أراها هي SignalEvent
. أنت لا تظهر لنا تنفيذ ذلك. إذا تم الإعلان عنه public event SignalEventDelegate SignalEvent
, ، ثم ستكون على ما يرام لأن المترجم يضيف تلقائيًا SynchronizedAttribute
. ومع ذلك، إذا SignalEvent
يستخدم مندوب دعم مع add
/remove
بناء جملة ، ستحتاج إلى توفير قفل الخاص بك للحدث نفسه ، وإلا سيكون من الممكن للمستهلك الانفصال عن الحدث بعد فوات الأوان ولا يزال يتلقى بعض الإشارات بعد ذلك.
تحرير: في الواقع ، هذا ممكن بغض النظر ؛ الأهم من ذلك ، إذا كنت قد استخدمت مندوبًا/إزالة على غرار خاصية بدون قفل مناسب ، فمن الممكن بالفعل أن يكون المندوب في حالة غير صالحة عند محاولة تنفيذها. حتى مع وجود حدث متزامن ، يحتاج المستهلكون إلى الاستعداد لتلقي (وتجاهل) الإخطارات بعد إلغاء الاشتراك.
بخلاف ذلك ، لا أرى أي مشاكل - على الرغم من أن هذا لا يعني أنه لا يوجد أي شيء ، فهذا يعني أنني لم ألاحظ أي شيء.
نصائح أخرى
أكبر مشكلة أراها هناك استخدام List<T>
لتنفيذ قائمة انتظار ؛ هناك مشكلات في الأداء في القيام بذلك ، لأن إزالة العنصر الأول يتضمن نسخ جميع البيانات.
أفكار إضافية أنت ترفع الإشارة حتى لو كنت لم يكن إضافة البيانات ، واستخدام الأحداث بحد ذاتها قد يكون لديك مشكلة في الخيوط (هناك بعض حالات الحافة ، حتى عند التقاط القيمة قبل null
الاختبار - بالإضافة إلى ذلك ربما أكثر من استخدام Monitor
للقيام بالإشارة).
أود أن أتحول إلى Queue<T>
التي لن لديها هذه المشكلة - أو أفضل استخدام مثال مسبقًا ؛ علي سبيل المثال إنشاء قائمة انتظار حظر في .NET؟, ، وهو ما تفعله بالضبط ما تناقشه ، ويدعم أي عدد من المنتجين والمستهلكين. يستخدم نهج الحظر ، ولكن نهج "المحاولة" سيكون:
public bool TryEnqueue(T item)
{
lock (queue)
{
if (queue.Count >= maxSize) { return false; }
queue.Enqueue(item);
if (queue.Count == 1)
{
// wake up any blocked dequeue
Monitor.PulseAll(queue);
}
return true;
}
}
أخيرًا - لا "تدفع" إلى كومة, ليس قائمة انتظار؟