معظم TPL تدفق البيانات تصميم مثالي؟

أود أن أسأل عن كيفية إدخال أفضل تصميم الهندسة المعمارية المثلى باستخدام TPL Dataflow. ليس لدي رمز مكتوب حتى الآن حتى لا يكون هناك رمز عينة يمكنني المشاركة. أنا لا أبحث عن الكود (ما لم تطوع) ولكن المساعدة في التصميم ستكون محل تقدير كبير:

المتطلبات هي كما يلي:

لدي 3 datablocks الأساسية التي تعتمد على بعضها البعض بطرق محددة. Datablock1 هو منتج ينتج كائنات من النوع Foo1. من المفترض أن يقوم Datablock2 بالاشتراك في كائنات Foo1 (من Datablock1) ويحتمل (ليس على كل Foo1 ، خاضعًا لوظيفة محددة) إنتاج كائنات Foo2 التي يخزنها في طابور إخراج لاستهلاك datablocks أخرى. يستهلك Datablock3 أيضًا كائنات Foo1 (من Datablock1) ويحتمل أن ينتج كائنات Foo3 التي يستهلكها Datablock2 ويتحول إلى كائنات Foo2.

باختصار ، فيما يلي قواعد البيانات وما ينتج عنه ويستهلكه:

  • Datablock1: Produces (Foo1)، Consumes (Nothing)
  • Datablock2: Produces (Foo2)، Consumes (Foo1، Foo3)
  • Datablock3: Produces (Foo3)، Consumes (Foo1)

هناك مطلب إضافي وهو أن نفس Foo1 تتم معالجته في نفس الوقت تقريبًا في Datablock2 و Datablock3. سيكون من الجيد إذا تم استهلاك كائنات Foo1 لأول مرة بواسطة Datablock2 وبعدها قام Datablock2 بعمله حيث تم نشر كائنات Foo1 نفسها إلى Datablock3 كي تقوم بعملها. يمكن أن تنتج كائنات Foo2 من Datablock2 من أي عمليات على كائنات Foo1 أو كائنات Foo3.

آمل أن يكون هذا منطقيًا ، ويسرني أن أشرح المزيد إذا كان لا يزال غير واضح.

كانت فكرتي الأولى هي إنشاء جداول بيانات TPL لكل من datablocks الثلاثة ولجعلها تتعامل مع التدفقات الواردة من أنواع الكائنات المختلفة. فكرة أخرى هي تقسيم datablocks وجعل كل datablock معالجة تدفقات من نوع كائن واحد فقط. ما الذي تنصح به أو هل هناك حل أفضل قد يعمل؟

ساعد Svick بالفعل على Datablock1 وهو بالفعل جاهز ، أنا فقط عالق على كيفية تغيير بيئتي الحالية (كما هو موضح أعلاه) إلى TPL Dataflow.

أي أفكار أو مؤشرات هي محل تقدير كبير.

1
أتفق تماما ، ولكن في هذه المرحلة لست متأكدا ما إذا كان يمكن حتى datablock الإعداد لقبول كائنات من ISourceBlocks مختلفة مرتبطة به. إذا كنت أعرف كيفية كتابة datablock التي يمكن أن تأخذ ، على سبيل المثال Foo1 و Foo2 ، قم بتخزينها في 2 قوائم انتظار منفصلة وتمتلك الوظيفة التي تم تمريرها تعمل إما عندها سأتمكن من إعداد بنية الاختبار بنفسي. المشكلة هي أن وثائق تدفق البيانات TPL حاليا غير موجودة إلى حد كبير. هل ستكون قادرًا على المساعدة في كيفية التعامل مع قبول العديد من المنتجين الذين يجتازون كائنات مختلفة إلى dataflowblock واحد؟
وأضاف المؤلف Matt Wolf, مصدر
انهم لا يشاركون في أي طبقة أساسية. الهدف هو معرفة ما إذا كان يمكن تصميم كتلة تأخذ/تعالج تيارات مختلفة (من كائنات الكتابة المختلفة). هذا هو الهدف الأساسي من هذا التمرين لمعرفة ما يمكن تنفيذه ، ليس لأن وثائق تدفق بيانات TPL ليس لديها الكثير لتقوله على الأقل لم أجد الكثير. تعثرت عبر BatchedJoinBlock ولكن لست متأكدا ما إذا كان ينطبق هنا ...
وأضاف المؤلف Matt Wolf, مصدر
@ svick ، ​​سؤالي هو ما إذا كان يمكن تصميم كتلة تدفق البيانات لقبول تدفقات من أنواع مختلفة من 2 datablocks منفصلة. أعتقد أنه يبدو كمتطلب معقول أن يوفر تدفق بيانات TPL طريقة واحدة أو أخرى ، ولكن فقط أن الوثائق متفرقة جدًا لدرجة أنني لم أر أي شيء يذكر أي نوع من أنواع الحظر يمكنه التعامل مع أنواع بيانات متعددة في حد ذاتها. طوابير الإدخال المعنية.
وأضاف المؤلف Matt Wolf, مصدر
إذا كنت تبحث عن بنية أكثر كفاءة ، فهناك واحد فقط للعثور عليه بشكل صحيح: قم بقياس خيارات مختلفة لمعرفة الأفضل في الواقع. كل شيء آخر سيكون مجرد تخمينات.
وأضاف المؤلف svick, مصدر
هل تشترك Foo1 و Foo3 في فئة أساسية مشتركة؟ إذا لم يكن الأمر كذلك ، فكيف يمكن أن تعالج الكتلة 2 على حد سواء؟
وأضاف المؤلف svick, مصدر

1 إجابة

دعونا تقسيم هذه المشكلة في ثلاثة وحل كل على حدة.

أول واحد هو كيفية إنتاج عنصر بشروط. أعتقد أن أفضل خيار هو استخدام TransformManyBlock والسماح لرسالتك بإرجاع مجموعة مع عنصر واحد أو صفر.

خيار آخر هو ربط الكتلتين بشكل مشروط ، بحيث يتم تجاهل null s وإرجاع فارغة عندما لا ترغب في إنتاج أي شيء. ولكن إذا قمت بذلك ، يجب عليك أيضًا ربط المصدر بـ NullTarget ، بحيث لا تبقى null s في المخزن المؤقت للإخراج الخاص بها.

المشكلة الثانية هي كيفية إرسال Foo1s إلى كل من كتلة # 2 والكتلة # 3. أستطيع أن أرى طريقتين هنا:

  1. استخدم BroadcastBlock مرتبطًا بكل من الكتل المستهدفة (# 2 و # 3). كن حذرًا في هذا ، نظرًا لأن BroadcastBlock لا يحتوي على قائمة انتظار للإخراج ، لذا إذا قام أحد المجموعات المستهدفة بتأجيل عنصر ما ، فهذا يعني أنه لن يقوم بمعالجته. وبسبب هذا ، يجب عليك عدم تعيين BoundedCapacity من الكتل رقم 2 و 3 في هذه الحالة. إذا لم تقم بذلك ، فلن يتم تأجيله وستتم معالجة كل الرسائل بواسطة الكتلتين.
  2. وبعد معالجة Foo1 من كتلة # 2، يدويا <�كود> المشاركة() </القانون> (أو أفضل، <�رمز> SendAsync() </القانون>) لمنع # 3. </لى>

لست متأكداً مما يعنيه بالضبط "في نفس الوقت تقريباً" ، ولكن بشكل عام ، لا يوفر TPL Dataflow أي ضمانات بشأن ترتيب معالجة الكتل المستقلة. يمكنك تغيير أولوية الكتل المختلفة باستخدام < > TaskScheduler ، ولكن لست متأكدًا من أنه سيكون مفيدًا هنا.

المشكلة الأخيرة والأكثر تعقيدًا هي كيفية معالجة عناصر من أنواع مختلفة في كتلة واحدة. هناك عدة طرق لكيفية القيام بذلك ، على الرغم من أنني لست متأكدًا من الأفضل لك:

  1. Don't process them in single block. Have one TransformBlock and one TransformBlock. You can then link them both to a single BufferBlock.
  2. As you suggested, use BatchedJoinBlock, with batchSize of 1. This means the resulting Tuple, IList> will always contain either one Foo1 or one Foo3.
  3. Enhance the previous solution by linking the BatchedJoinBlock to a TransformBlock that produces a more suitable type. That could be either Tuple (one of the items will be always null), or something like the F# Choice, which ensures that only one of the two is set.
  4. Create a new block type from scratch, that does exactly what you want. It should be ISourceBlock and also have two properties: Target1 of type ITarget and Target2 of type ITarget, like the built-in join blocks.

باستخدام الخيارين 1 و 3 ، يمكنك أيضًا تجسير الكتل في كتلة مخصصة واحدة ، تشبه الكتلة من # 4 من الخارج ، بحيث يمكن إعادة استخدامها بسهولة أكبر.

4
وأضاف
هل يمكنني توضيح ما تعنيه بـ "إنتاج بشروط"؟ هل هذا ضروريًا نظرًا لأن TransformBlocks تضع عادةً نفس الكمية من العناصر في قائمة انتظار الإخراج بشكلٍ أكبر من الوصول إلى قائمة انتظار الإدخال؟ هل سيكون استخدام Actionblock والإرسال على الشرط قابلا قذرا؟
وأضاف المؤلف Matt Wolf, مصدر
مع "في نفس الوقت تقريبا" أردت أن أشير إلى حقيقة أن Foo3s التي يتم إنتاجها بواسطة DataBlock3 والتي تستهلكها DataBlock2 تعتمد على حالات بعض المتغيرات التي تم تعيينها في Datablock2 بواسطة Foo1s. أعتقد أن الطريقة الوحيدة حول هذه المشكلة هي دمج Datablocks 2 و 3. أو استخدام DataBlock مختلفة لكل نوع دفق.
وأضاف المؤلف Matt Wolf, مصدر
اخترت الخيار # 1 ، ينتج عنه رمز أنظف ويثبت بالسرعة الكافية. سؤال واحد على الرغم من: هل هناك طريقة في Actionblock بانتظار معالجة العنصر التالي في قائمة الانتظار حتى تتم معالجة العنصر الحالي بشكل كامل ليس فقط داخل الإجراء نفسه ولكن إذا تم تمرير العنصر عبر Post (item) إلى كتلة بيانات أخرى. هل هناك نوع من الآلية للإبلاغ عن معالجة العنصر الحالي بشكل كامل وإرشاد Actionblock لمعالجة العنصر التالي في قائمة الانتظار؟
وأضاف المؤلف Matt Wolf, مصدر
نشكرك على مشاركة خبرتك الواسعة في TPL Dataflow. عذرا ، أنا لست مبرمجا مدربا وبالتالي لا أعرف ، هل يحدث أن تكون على فريق تطوير MS TPL Dataflow؟
وأضاف المؤلف Matt Wolf, مصدر
وضعت علامة على إجابتك على أنها الحل المنشود لأنها جعلتني أذهب وقررت في النهاية تنفيذ كتل بيانات فردية تقوم كل عملية بمعالجة نوع var مميز. أعتقد أنها أفضل مفاضلة بين السرعة الخام ونظافة الشفرة والأناقة. أضطر أحيانًا لاستخدام Post/SendAsync بدلاً من ربط datablocks ولكن أعتقد أنني لن أتجاوزها بسهولة عند إخراج العناصر بترددات مختلفة عند وصول العناصر. نشكرك مرة أخرى على وقتك وجهودك للمساعدة في هذا الأمر .
وأضاف المؤلف Matt Wolf, مصدر
نعم ، فالانتاج المشروط يعني أن بعض المدخلات فقط تنتج مخرجات. وتنتج TransformBlock نفس الكمية من العناصر التي تستهلكها ، نعم. بشكل عام ، أعتقد أن الربط أفضل من النشر ، ولكن في هذه الحالة ، أعتقد أنه خيار صالح.
وأضاف المؤلف svick, مصدر
ووجود كتلة واحدة تعتمد على بعض البيانات الخارجية يتعارض مع فكرة تدفق بيانات TPL (ونموذج الممثل المبني عليه). إذا احتاجت بعض المقاطع إلى شيء محدد لعنصر ما ، فيجب أن تأتي مع هذا العنصر. وبسبب ذلك ، من المحتمل أن يكون الدليل Post() هو الخيار الأفضل هنا ، وسوف يتأكد من صحة ترتيب العمليات.
وأضاف المؤلف svick, مصدر
الفكرة الكاملة لتدفق بيانات TPL هي أن المعالجة في كل كتلة مستقلة. إذا كنت ترغب في الانتظار حتى الانتهاء من معالجة بعض العناصر ، فعندئذ أعتقد أن هذا الجزء لا ينبغي أن يكون كتلة خاصة به. يمكنك أن تجعله طريقة عادية تتصل بها من ActionBlock .
وأضاف المؤلف svick, مصدر
أشعر بالاطمئنان وأعتقد ذلك ، ولكن ليس لدي أي علاقة مع مايكروسوفت أو فريق Dataflow.
وأضاف المؤلف svick, مصدر
لقد اكتشفت طريقة لإنتاج 0 أو 1 عناصر من TransformBlock : كتابة transform lambda وترجع Task.FromResult (القيمة) للعودة 1 عنصر وإرجاع فارغة المهمة لإرجاع 0 عناصر. قد يكون استخدام TransformManyBlock أكثر وضوحًا ، ولكن قد يكون هذا أيضًا خيارًا مثيرًا للاهتمام.
وأضاف المؤلف svick, مصدر