خيوط السليم في كيو تي

Qt إطار قوي ومناسب للغاية لـ C ++. ولكن هذه الراحة لها جانب سلبي: الكثير من الأشياء في كيو تي تحدث مخفية عن المستخدم. في معظم الحالات ، تعمل الوظيفة المقابلة في Qt بطريقة "سحرية" ، وهي تعلم المستخدم أن يأخذ هذا السحر ببساطة كأمر مسلم به. ومع ذلك ، عندما ينهار السحر ، من الصعب للغاية التعرف على مشكلة تظهر فجأة على مستوى يبدو مسطحًا وحلها.

هذه المقالة محاولة لتنظيم كيفية تنفيذ Qt "تحت الغطاء" للعمل مع التدفقات وحول عدد من المزالق غير الواضحة المرتبطة بالقيود المفروضة على هذا النموذج.

الأساسيات
تقارب الموضوع ، التهيئة ، وقيودها
الموضوع الرئيسي ، QCoreApplication واجهة المستخدم الرسومية
تقديم موضوع
استنتاج

الأساسيات


لنبدأ بالأساسيات. في Qt ، أي كائنات قادرة على التعامل مع الإشارات وفتحات هي أحفاد فئة QObject. هذه الكائنات حسب التصميم غير قابلة للنسخ وتمثل منطقيًا بعض الكيانات الفردية التي "تتحدث" مع بعضها البعض - تتفاعل مع أحداث معينة ويمكنها أن تنشئ أحداثًا بنفسها. بمعنى آخر ، يقوم QObject في Qt بتنفيذ نمط الممثلين . إذا تم تنفيذه بشكل صحيح ، فإن أي برنامج من برامج Qt لا يعدو أن يكون أكثر من شبكة من QObjects التي تتفاعل مع بعضها البعض حيث "يعيش" كل منطق البرنامج.

بالإضافة إلى مجموعة من QObjects ، يمكن أن يتضمن برنامج Qt كائنات البيانات. لا يمكن لهذه الكائنات توليد واستقبال الإشارات ، ولكن يمكن نسخها. على سبيل المثال ، يمكنك مقارنة QStringList و QStringListModel فيما بينها. أحدهما هو QObject وهو غير قابل للنسخ ، لكن يمكنه التفاعل مباشرة مع كائنات واجهة المستخدم ، والآخر عبارة عن حاوية عادية قابلة للنسخ للبيانات. بدوره ، يتم تقسيم الكائنات التي تحتوي على بيانات إلى "أنواع Meta Qt" وجميع الأنواع الأخرى. على سبيل المثال ، QStringList عبارة عن Qt Meta-type ، لكن std :: list <std :: string> (بدون إيماءات إضافية) ليست كذلك. يمكن استخدام السابق في أي سياق Qt-shnom (يتم إرساله عبر الإشارات ، والكذب في QVariant ، وما إلى ذلك) ، ولكن يتطلب إجراء تسجيل خاص ويجب أن يكون للفصل مدمر عام ومنشئ نسخ ومنشئ افتراضي. والثاني أنواع C ++ التعسفي.

الانتقال بسلاسة إلى مؤشرات الترابط الفعلية


لذلك ، لدينا "بيانات" مشروطة وهناك "رمز" شرطي يعمل معها. لكن من الذي سينفذ هذا الكود فعلاً؟ في نموذج Qt ، يتم تعيين الإجابة على هذا السؤال بشكل صريح: يرتبط كل QObject ارتباطًا وثيقًا ببعض خيط QThread والذي ، في الواقع ، يشارك في فتحات الخدمة وأحداث أخرى من هذا الكائن. يمكن أن يخدم مؤشر ترابط واحد العديد من QObjects في وقت واحد ، أو لا يوجد شيئًا على الإطلاق ، لكن QObject يحتوي دائمًا على مؤشر ترابط أصلي وهو دائمًا واحد. في الواقع ، يمكننا أن نفترض أن كل QThread "يملك" مجموعة من QObject. في مصطلحات كيو تي ، وهذا ما يسمى تقارب الموضوع. دعونا نحاول أن نتصور الوضوح:



يوجد داخل كل QThread قائمة انتظار من الرسائل الموجهة إلى الكائنات التي يمتلكها هذا QThread. في نموذج Qt ، من المفترض أننا إذا أردنا أن يتخذ QObject بعض الإجراءات ، فإننا "نرسل" رسالة QEvent إلى QObject:

QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority); 

في هذه المكالمة الآمنة لمؤشر الترابط ، يبحث Qt عن QThread الذي ينتمي إليه كائن المتلقي ، ويكتب QEvent في قائمة انتظار الرسائل في هذا الموضوع ، ويستيقظ هذا الخيط إذا لزم الأمر. من المتوقع أن يقوم الرمز الذي يتم تشغيله في QThread هذا في وقت ما بعد ذلك بقراءة الرسالة من قائمة الانتظار وتنفيذ الإجراء المقابل. لكي يحدث هذا بالفعل ، يجب أن تدخل التعليمة البرمجية الموجودة في QThread في حلقة أحداث QEventLoop ، وأن تنشئ الكائن المناسب وتدعوه إما بالطريقة exec () أو بالطريقة processEvents (). يدخل الخيار الأول في حلقة لا نهائية لمعالجة الرسائل (قبل أن يستقبل QEventLoop الحدث quit ()) ، ويقتصر الخيار الثاني على معالجة الرسائل التي تراكمت مسبقًا في قائمة الانتظار.



من السهل معرفة أنه تتم معالجة الأحداث لجميع الكائنات التي تنتمي إلى مؤشر ترابط واحد بالتتابع. إذا استغرقت معالجة حدث بواسطة سلسلة رسائل وقتًا طويلاً ، فسيتم "تجميد" جميع الكائنات الأخرى - تتراكم أحداثها في قائمة انتظار الدفق ، لكن لن تتم معالجتها. لمنع حدوث ذلك ، توفر Qt إمكانية تعدد المهام التعاوني - يمكن لمعالجات الأحداث في أي مكان "المقاطعة مؤقتًا" عن طريق إنشاء QEventLoop جديد وتمرير التحكم فيه. نظرًا لأنه تم استدعاء معالج الأحداث مسبقًا من QEventLoop في الدفق ، مع هذا النهج ، يتم تشكيل سلسلة من حلقات الأحداث "المتداخلة" في بعضها البعض.

بضع كلمات عن مرسل الحدث
بالمعنى الدقيق للكلمة ، QEventLoop ليس أكثر من مجرد برنامج سهل الاستخدام على مستوى بدائي يعتمد على النظام منخفض المستوى يسمى Event Dispatcher وينفذ واجهة QAbstractEventDispatcher. هو الذي يقوم بجمع ومعالجة الأحداث الفعلية. يمكن أن يحتوي مؤشر الترابط على QAbstractEventDispatcher واحد فقط ويتم تثبيته مرة واحدة فقط. من بين أشياء أخرى ، بدءًا من Qt5 ، يتيح لك ذلك استبدال المرسل بسهولة بخط أكثر ملاءمة إذا لزم الأمر بإضافة سطر واحد فقط لتهيئة البث ودون لمس الأماكن الكثيرة المحتملة حيث يتم استخدام QEventLoop.

ما هو مدرج في مفهوم "الحدث" معالجتها في هذه الدورة؟ تعتبر "الإشارات" المعروفة لجميع موظفي Qt ، مجرد مثال واحد معين ، QEvent :: MetaCall. يقوم QEvent بتخزين مؤشر على المعلومات اللازمة لتحديد الوظيفة (الفتحة) التي يجب استدعاؤها ووسائطها. ومع ذلك ، بالإضافة إلى الإشارات الموجودة في Qt ، هناك حوالي مائة (!) أحداث أخرى ، منها عشرة محجوزة لأحداث Qt الخاصة (Child المضافة ، و DeferredDelete ، و ParentChange) والبقية تتوافق مع الرسائل المختلفة من نظام التشغيل.

لماذا يوجد الكثير منهم ولماذا كان من المستحيل الاستغناء عن الإشارات فقط؟
قد يتساءل القارئ: لماذا توجد الكثير من الأحداث ولماذا كان من المستحيل تخطيها باستخدام آلية إشارة واحدة مريحة وعالمية؟ والحقيقة هي أن الإشارات المختلفة يمكن معالجتها بشكل مختلف جدا. على سبيل المثال ، تكون بعض الإشارات قابلة للضغط - إذا كانت قائمة الانتظار تحتوي بالفعل على رسالة أولية واحدة من هذا النوع (مثل QEvent :: Paint) ، فإن الرسائل اللاحقة تعدلها ببساطة. إشارات أخرى يمكن تصفيتها. إن وجود عدد صغير من QEvents القياسية والتي يمكن تحديدها بسهولة يسهل بشكل كبير المعالجة المقابلة. بالإضافة إلى ذلك ، تتم عادةً معالجة QEvent بسبب جهاز أبسط بشكل ملحوظ إلى حد ما بشكل أسرع من معالجة إشارة مماثلة.

أحد المزالق غير الواضحة هنا هو أنه في Qt ، قد لا يحتوي مجرى ، عمومًا ، على مرسل ، وبالتالي لا يحتوي على EventLoop واحد. الكائنات التابعة لهذا التدفق لن تستجيب للأحداث المرسلة إليهم. نظرًا لأن QThread :: run () افتراضيًا يستدعي QThread :: exec () بداخله يتم تطبيق EventLoop القياسي فقط ، والذين يواجهون غالبًا تحديد إصدار خاص بهم من run () يرثون من QThread غالبًا ما يواجهون هذه المشكلة. تعتبر حالة الاستخدام المماثلة لـ QThread صالحة من حيث المبدأ وتوصي حتى في الوثائق ، لكنها تتعارض مع الفكرة العامة لتنظيم التعليمات البرمجية في Qt الموضحة أعلاه وغالبًا لا تعمل كما يتوقع المستخدمون. خطأ نموذجي في هذه الحالة هو محاولة لإيقاف مثل هذا QThread مخصص عن طريق استدعاء QThread :: exit () أو إنهاء (). ترسل هاتان الوظيفتان رسالة إلى QEventLoop ، ولكن إذا لم يكن هناك QEventLoop في الدفق ، فلا يوجد أحد يعالجها. نتيجة لذلك ، يبدأ المستخدمون الذين لا يتمتعون بالخبرة والذين يحاولون "إصلاح فئة مقطوعة" في محاولة استخدام QThread :: إنهاء "عاملة" ، وهو أمر مستحيل تمامًا. ضع في اعتبارك - إذا قمت بإعادة تعريف run () ولم تستخدم حلقة الأحداث القياسية ، فسيتعين عليك توفير آلية لإنهاء سلسلة الرسائل بنفسك - على سبيل المثال ، باستخدام وظيفة QThread :: requestInterruption () المضافة لهذا الغرض. ومع ذلك ، فمن الأصح ألا ترث ببساطة من QThread إذا كنت لا تريد تطبيق نوع جديد من الخيوط الخاصة وإما أن تستخدم QtConcurrent الذي تم إنشاؤه خصيصًا لهذه البرامج النصية ، أو ضع المنطق في كائن عامل خاص موروث من QObject ، ضع الأخير في QThread وإدارته العامل باستخدام الإشارات.

تقارب الموضوع ، التهيئة ، وقيودها


لذلك ، كما سبق أن توصلنا ، فإن كل كائن في Qt "ينتمي" إلى بعض الدفق. في الوقت نفسه ، يطرح سؤال منطقي: إلى أيهما بالضبط؟ يتم قبول الاتفاقيات التالية في كيو تي:

1. جميع "الأطفال" من أي "الوالد" يعيش دائما في نفس تيار الوالد

ربما يكون هذا هو القيد الأقوى لنموذج قطار Qt ، وغالبًا ما تعطي محاولات كسره نتائج غريبة جدًا للمستخدم. على سبيل المثال ، فشلت محاولة جعل setParent على كائن يعيش في مؤشر ترابط آخر في Qt ببساطة بصمت (تحذير مكتوب إلى وحدة التحكم). على ما يبدو ، تم التوصل إلى هذا الحل الوسط بسبب حقيقة أن "إزالة" الخيط الآمن لـ "الأطفال" في حالة وفاة أحد الوالدين الذين يعيشون في خيط آخر أمر غير تافه للغاية وعرضة لصعوبة الحشرات. إذا كنت تريد تطبيق تسلسل هرمي للكائنات التفاعلية التي تعيش في تدفقات مختلفة ، فسيتعين عليك تنظيم الحذف بنفسك.

2. الكائن الذي لم يتم تحديد أصله أثناء الخلق يعيش في التدفق الذي أنشأه

كل شيء هنا في نفس الوقت وببساطة وفي نفس الوقت ليس واضحًا دائمًا. على سبيل المثال ، بحكم هذه القاعدة ، يعيش QThread (ككائن) في مؤشر ترابط مختلف عن مؤشر الترابط الذي يتحكم فيه (وبحكم القاعدة 1 ، لا يمكنه امتلاك أي من الكائنات التي تم إنشاؤها في هذا الموضوع). أو ، على سبيل المثال ، إذا قمت بإعادة تعريف QThread :: run وإنشاء أي من أحفاد QObject بالداخل ، ثم دون اتخاذ تدابير خاصة (كما تمت مناقشته في الفصل السابق) ، فإن الكائنات المنشأة لن تستجيب للإشارات.

يمكن تغيير تقارب الموضوع إذا لزم الأمر عن طريق استدعاء QObject :: moveToThread. بموجب القاعدة 1 ، يمكن فقط نقل "الوالدين" من المستوى الأعلى (الذي يكون الوالد = لاغٍ له) ، وسيتم تجاهل محاولة نقل أي "طفل" بصمت. عندما يتحرك "الوالد" ذي المستوى الأعلى ، ينتقل جميع "أطفاله" أيضًا إلى جدول جديد. من الغريب أن الدعوة إلى moveToThread (nullptr) هي أيضًا قانونية وهي وسيلة لإنشاء كائن ذي تقارب مؤشر ترابط "null" ؛ هذه الكائنات لا يمكن أن تتلقى أي رسائل.

يمكنك الحصول على مؤشر الترابط "الحالي" للتنفيذ من خلال استدعاء دالة QThread :: currentThread () ، والتي يرتبط بها الكائن - من خلال استدعاء QObject :: thread ()

سؤال مثير للاهتمام على الاهتمام
لاحظ أن تنفيذ وظيفة ملكية الكائنات وتخزين QEvents الموجه إليهم ، من الواضح ، يتطلب التدفق لتخزين البيانات المقابلة في مكان ما. في حالة كيو تي ، تشارك الفئة الأساسية كيوريد عادة في استخراج وإدارة هذه البيانات. ولكن ماذا يحدث إذا قمت بإنشاء QObject في بعض مؤشرات الترابط :: أو قمت باستدعاء الدالة QThread :: currentThread () من هذا الموضوع؟ اتضح أنه في هذه الحالة ، ستقوم كيو تي ضمنيًا "وراء الكواليس" بإنشاء كائن مجمع خاص لا يمتلك QAdoptedThread. في الوقت نفسه ، يتعين على المستخدم التأكد بشكل مستقل من حذف جميع الكائنات الموجودة في هذا الدفق قبل إيقاف الدفق الذي أنشأها.

الموضوع الرئيسي ، QCoreApplication واجهة المستخدم الرسومية


من بين جميع المواضيع ، ستقوم شركة Qt بالتأكيد بتفصيل "خيط رئيسي" واحد ، والذي في حالة تطبيقات واجهة المستخدم يصبح خيط واجهة المستخدم الرسومية. في مؤشر الترابط هذا ، يعيش كائن QApplication (QCoreApplication / QGuiApplication) الذي يخدم حلقة الحدث الرئيسية الموجهة للعمل مع الرسائل من نظام التشغيل. بموجب القاعدة رقم 2 من القسم السابق ، من الناحية العملية ، سيكون الخيط "الرئيسي" هو الذي أنشأ فعلاً كائن QApplication ، ولأن "الخيط الرئيسي" في العديد من أنظمة التشغيل له معنى خاص ، فإن الوثائق توصي بشدة بإنشاء QApplication مع الكائن الأول للغاية في الكل كيو تي برنامج والقيام بذلك مباشرة بعد بدء التطبيق (== داخل الموضوع الأول في العملية). للحصول على مؤشر إلى مؤشر الترابط الرئيسي للتطبيق ، على التوالي ، يمكنك استخدام إنشاء النموذج QCoreApplication :: مثيل () -> thread (). ومع ذلك ، من الناحية الفنية البحتة ، يمكن تعليق QApplication أيضًا على دفق غير رئيسي () ، على سبيل المثال ، إذا تم إنشاء واجهة Qt داخل نوع من المكونات الإضافية وفي كثير من الحالات ، سيكون هذا جيدًا.

نظرًا لقاعدة "الكائنات التي تم إنشاؤها ترث الخيط الحالي" ، يمكنك دائمًا العمل بهدوء دون تجاوز حدود موضوع واحد. ستنتقل جميع الكائنات التي تم إنشاؤها تلقائيًا إلى مؤشر الترابط "الرئيسي" للصيانة ، حيث ستكون هناك دائمًا حلقة حدث و (بسبب عدم وجود مؤشرات ترابط أخرى) لن تكون هناك مشكلات في المزامنة. حتى إذا كنت تعمل مع نظام أكثر تعقيدًا يتطلب تعدد العمليات ، فمن المحتمل أن تندرج معظم الكائنات في الدفق الرئيسي ، باستثناء القليل منها الذي سيتم وضعه بشكل صريح في مكان آخر. ربما هذا هو بالضبط الظروف التي تؤدي إلى "السحر" الظاهر الذي يبدو أن الأشياء تعمل فيه بشكل مستقل دون أي جهد (لأن تنفيذ تعدد المهام التعاوني داخل الدفق) وفي الوقت نفسه لا يتطلب التزامن أو الحجب أو ما شابه (لأن كل شيء يحدث في دفق واحد ).

إلى جانب حقيقة أن الخيط "الرئيسي" هو الخيط "الأول" ويحتوي على حلقة معالجة حدث QCoreApplication الرئيسية ، هناك ميزة أخرى تحد من Qt وهي أن جميع الكائنات المرتبطة بـ GUI يجب أن "تعيش" في هذا الخيط. هذا جزئيًا نتيجة Legacy: نظرًا لحقيقة أنه في عدد من أنظمة التشغيل يمكن أن تحدث أي عمليات مع واجهة المستخدم الرسومية فقط في سلسلة الرسائل الرئيسية ، يقسم Qt جميع الكائنات إلى "عناصر واجهة مستخدم" و "غير عناصر واجهة مستخدم". يمكن لكائن نوع القطعة أن يعيش فقط في الخيط الرئيسي ، فإن محاولة "تفوق" مثل هذا الكائن في أي شيء آخر ستشتعل تلقائيًا. بفضل هذا ، هناك حتى طريقة QObject :: isWidgetType () خاصة تعكس الاختلافات الداخلية العميقة في آليات العمل مع مثل هذه الكائنات. ولكن من المثير للاهتمام أنه في QtQuick الأحدث بكثير ، حيث حاولوا الابتعاد عن العكاز باستخدام isWidgetType ، بقيت نفس المشكلة

ما هو الموضوع؟ في Qt5 ، لم تعد كائنات QML عبارة عن عناصر واجهة مستخدم ويمكن عرضها في سلسلة رسائل منفصلة. ولكن هذا أدى إلى مشكلة أخرى - صعوبات التزامن. يمثل تقديم كائنات واجهة المستخدم "قراءة" لحالتها ويجب أن يكون متسقًا: إذا حاولنا تغيير حالة كائن ما في نفس وقت تقديمه ، فإن نتيجة "العرق" الناتجة قد لا ترضينا. بالإضافة إلى ذلك ، يتم شحذ OpenGL الذي يتمحور حوله الرسومات "Qt" الجديدة "Qt" إلى حد كبير بحيث يتم تنفيذ أوامر الرسم بواسطة مؤشر ترابط واحد يعمل مع بعض الحالات العالمية - "سياق الرسم" الذي لا يمكن تغييره إلا كسلسلة من العمليات المتسلسلة. ببساطة لا يمكننا رسم كائنين رسوميين مختلفين على الشاشة - سيتم رسمهما دائمًا بالتتابع واحدًا تلو الآخر. نتيجة لذلك ، نعود إلى نفس الحل - مما يجعل واجهة المستخدم مخصصة لخيط واحد. ومع ذلك ، سوف يلاحظ القارئ المهتم أن هذا الخيط ليس من الضروري أن يكون الخيط الرئيسي - وفي Qt5 سيحاول الإطار حقًا استخدام خيط تجسيد منفصل لهذا

تقديم موضوع


في إطار نموذج Qt5 الجديد ، تتم جميع عمليات تقديم الكائنات في سلسلة رسائل مخصصة لهذا الغرض ، سلسلة عمليات العرض. علاوة على ذلك ، ولكي يكون الأمر منطقيًا وليس مقصورًا على مجرد التبديل من دفق "رئيسي" إلى آخر ، فإن الكائنات تنقسم ضمنيًا إلى "واجهة أمامية" يراها المبرمج وعادةً ما تكون "خلفية خلفية" مخفية عنه تؤدي فعليًا العرض الفعلي. تعيش النهاية الخلفية في الخيط التقديم ، بينما يمكن للجهة الأمامية ، من الناحية النظرية ، أن تعيش في أي خيط آخر. من المفترض أن الواجهة الأمامية تؤدي العمل المفيد (إن وجد) في شكل معالجة الحدث ، في حين أن وظيفة الواجهة الخلفية محدودة فقط عن طريق التقديم. من الناحية النظرية ، تبين أنه يربح الجميع: ظهر "الاستطلاعات" الدورية للحالة الراهنة للكائنات وترسمها على الشاشة ، بينما لا يمكن "إيقافها" بحقيقة أن بعض الكائنات "تفكر" كثيرًا أثناء معالجة الحدث نظرًا لحقيقة أن هذا معالجة بطيئة يحدث في موضوع آخر. بدوره ، لا يحتاج تدفق الكائن إلى انتظار "إجابات" من برنامج تشغيل الرسومات لتأكيد اكتمال التجسيد ، ويمكن للكائنات المختلفة أن تعمل في تدفقات مختلفة.

ولكن كما ذكرت بالفعل في الفصل السابق ، نظرًا لأن لدينا دفقًا يقوم بإنشاء البيانات (واجهة) ودفقًا يقرأها (للخلف) ، نحتاج إلى مزامنتها بطريقة أو بأخرى. تتم هذه المزامنة في كيو تي بواسطة الأقفال. يتم تعليق البث المباشر للحياة الأمامية ، متبوعًا باستدعاء وظيفة خاصة (QQuickItem :: updatePaintNode () ، QQuickFramebufferObject :: Renderer :: synchronize () وتتمثل مهمته الوحيدة في نسخ الكائن ذي الصلة بالتخيل من الأمام إلى الخلف ". في هذه الحالة ، يحدث استدعاء مثل هذه الوظيفة داخل مؤشر ترابط التجسيد ، ولكن نظرًا لحقيقة أن الخيط الذي يعيش فيه الكائن في هذه اللحظة قد توقف ، يمكن للمستخدم العمل بحرية مع بيانات الكائن كما لو أنه حدث "كالمعتاد" ، داخل الدفق الذي ينتمي إليه الكائن.

هل كل شيء على ما يرام ، هل كل شيء على ما يرام؟ لسوء الحظ ، لا ، ولحظات غير واضحة تماما تبدأ هنا. إذا أخذنا قفلًا فرديًا لكل كائن ، فسيكون ذلك بطيئًا إلى حد ما لأن مؤشر ترابط التجسيد سيضطر إلى الانتظار حتى تنتهي هذه الكائنات من معالجة أحداثها. تيار "تعليق" حيث يعيش الكائن هو "تعليق" وعرضه. بالإضافة إلى ذلك ، سيصبح "desync" ممكنًا ، عندما يتم تغيير كائنين في وقت واحد ، سيتم رسم أحدهما في الإطار N وسيتم رسم الآخر فقط في الإطار N + 1. سيكون من الأفضل أخذ القفل مرة واحدة فقط ولجميع الكائنات مرة واحدة وفقط عندما نكون متأكدين من نجاح هذا القفل.

ما الذي تم تنفيذه لحل هذه المشكلة في كيو تي؟ أولاً ، تقرر أن جميع الكائنات "الرسومية" لإطار واحد ستعيش في دفق واحد. وبالتالي ، لرسم نافذة وتأمين جميع الكائنات الموجودة فيها ، يصبح من الكافي إيقاف هذا التدفق بمفرده. ثانياً ، يبدأ مؤشر الترابط مع كائنات واجهة المستخدم في تأمين تحديث الخلفية ، وإرسال رسالة إلى تقديم مؤشر ترابط حول الحاجة إلى مزامنة وإيقاف نفسه (QSGThreadedRenderLoop :: polishAndSync إذا كان أي شخص مهتمًا). هذا يضمن أن مؤشر ترابط العرض لن "ينتظر" أبدًا دفقًا أماميًا. إذا "توقف" فجأة ، فسيستمر مؤشر الترابط الخاص بالرسم في رسم الحالة "القديمة" للكائنات دون تلقي رسائل حول الحاجة إلى التحديث. يؤدي هذا حقًا إلى ظهور أخطاء مسلية تمامًا في الشكل "إذا كان السبب لا يمكن للعرض رسم النافذة على الفور ، فإن الخيط الرئيسي يتجمد" ، ولكن بشكل عام يعد هذا حل وسطًا معقولًا. بدءًا من الإصدار 2.0 من QtQuick ، ​​يمكن حتى "ملؤها" عدد من الكائنات "المتحركة" في سلسلة التجسيد بحيث يمكن أن تستمر الرسوم المتحركة في العمل إذا كان الفكر الرئيسي هو "فكر".



ومع ذلك ، فإن النتيجة العملية لهذا الحل هي أن جميع كائنات واجهة المستخدم يجب أن تعيش في نفس الموضوع على أي حال. في حالة عناصر واجهة التعامل القديمة ، في الخيط "الرئيسي" ، في حالة كائنات Qt Quick الجديدة ، في خيط كائن QQuickWindow الذي يمتلكها. يتم ضرب القاعدة الأخيرة بشكل أنيق - من أجل رسم QQuickItem ، فإنه يحتاج إلى جعل setParent إلى QQuickWindow المطابق والذي ، كما تمت مناقشته بالفعل ، يضمن أن الكائن إما ينتقل إلى الدفق المقابل أو فشل استدعاء setParent.

والآن ، للأسف ، ذبابة في المرهم: على الرغم من أن QQuickWindow مختلفة يمكن أن تعيش نظريًا في تدفقات مختلفة ، إلا أن هذا يتطلب في الواقع إرسال رسائل دقيقة من نظام التشغيل إليهم وفي كيو تي اليوم لم يتم تطبيقه. في Qt 5.13 ، على سبيل المثال ، يحاول QCoreApplication التواصل مع QQuickWindow عبر sendEvent التي تتطلب من المتلقي والطرف المرسل أن يكونا في نفس مؤشر الترابط (بدلاً من postEvent الذي يسمح بأن تكون مؤشرات الترابط مختلفة). لذلك ، في الممارسة العملية ، QQuickWindow يعمل فقط بشكل صحيح في مؤشر ترابط واجهة المستخدم الرسومية ، ونتيجة لذلك ، تعيش جميع كائنات QtQuick في نفس المكان. نتيجة لذلك ، على الرغم من وجود مؤشر ترابط التجسيد ، لا تزال جميع الكائنات المرتبطة بـ GUI المتوفرة للمستخدم موجودة في نفس مؤشر ترابط واجهة المستخدم الرسومية. ربما هذا سوف يتغير في كيو تي 6.

بالإضافة إلى ما سبق ، تجدر الإشارة إلى أنه نظرًا لأن Qt يعمل على العديد من الأنظمة الأساسية المختلفة (بما في ذلك الأنظمة التي لا تدعم مؤشرات ترابط متعددة) ، فإن الإطار يوفر عددًا لا يستهان به من عمليات الاسترجاع وفي بعض الحالات يتم تنفيذ وظيفة مؤشر الترابط في الواقع بواسطة نفس مؤشر ترابط واجهة المستخدم الرسومية . في هذه الحالة ، تعيش واجهة المستخدم بأكملها ، بما في ذلك التقديم ، في مؤشر ترابط واحد وتختفي مشكلة المزامنة تلقائيًا. يتشابه الموقف مع واجهة المستخدم القديمة المستندة إلى عنصر واجهة مستخدم Qt4. Qt «» QSG_RENDER_LOOP .

استنتاج


Qt — . , , Qt .

;

  • «» , queued signals
  • «» Qt Event Loop exit()
  • . top-level parent. setParent moveToThread
  • GUI- rendering back-end GUI-
  • GUI- QApplication

Qt

Source: https://habr.com/ru/post/ar467261/


All Articles