ما الذي يعنيه «لا يستجيب» حقّاً ── كيف يقرِّر Windows أنّ تطبيقاً تجمّد، وكيف تصمِّم تطبيقات لا تتجمّد

· آخر تحديث: · · Windows, تطوير Windows, استكشاف الأخطاء, تعدّد مؤشّرات الترابط, WinForms, WPF, Win32 API, تصميم الواجهة

سجل التعديلات (النسخة الأولى، نُشرت في 22 Aug، 2026)
النشر الأول
الاستشهاد بهذا المقال(DOI (الأرشيف المسجّل): 10.5281/zenodo.22176657)

تشير معرّفات DOI أدناه إلى إصدارات مؤرشفة سابقًا، وقد تختلف عن النص الحالي. للإشارة إلى النص الحالي، استخدم رابط هذه الصفحة.

غو كومورا (2026). ما الذي يعنيه «لا يستجيب» حقّاً ── كيف يقرِّر Windows أنّ تطبيقاً تجمّد، وكيف تصمِّم تطبيقات لا تتجمّد. شركة كومورا سوفت ذ.م.م.. https://comcomponent.com/ar/blog/windows-app-not-responding-hang-mechanism/

DOI (الأرشيف المسجّل)
10.5281/zenodo.22176657
DOI (آخر إصدار مسجّل)
10.5281/zenodo.22241134

«تصير الشاشة بيضاء في منتصف المعالجة ويظهر في العنوان «لا يستجيب».» «يقول المستخدمون إنّه يتجمّد أحياناً، لكنّه لا يُعاد إنتاجه أبداً على جهاز التطوير.» هذه شكاوى شائعة عن تطبيقات أعمال Windows.

أوّل ما ينبغي استيعابه أنّ من يعرض «لا يستجيب» هو Windows، لا التطبيق نفسه. يكتشف Windows أنّ معالجة رسائل النافذة توقّفت، ويستبدل الشاشة الأصليّة بنافذة بديلة. مفتاح تتبّع السبب هو ماذا يفعل مؤشّر ترابط الواجهة الذي يملك تلك النافذة، بحيث لا يستطيع معالجة الرسالة التالية.12

هذا المقال موجَّه إلى المطوِّرين الذين يبنون تطبيقات أعمال على Windows وإلى موظّفي تقنيّة المعلومات الذين يستقبلون تذاكر عن تطبيقات تتجمّد. يسير بالترتيب حكم نظام التشغيل ← حلقة الرسائل ← عزل السبب حسب الفئة ← تصاميم لا تتجمّد ← إجراء التحقيق. إن احتجت إلى تحقيق تطبيق معلَّق الآن، اقرأ الفصل 7 أوّلاً.

1. الخلاصة أوّلاً: أخلِ مؤشّر ترابط الواجهة، لا تخفِ العرض

آليّة «لا يستجيب»، وطريقة الإصلاح، وطريقة التحقيق تصير أوضح بكثير حين تُقسَم كما يلي.

ما تريد معرفته أو ما تواجهه أوّل ما تستوعبه التفاصيل
ماذا ينظر Windows ليقرِّر أنّ تطبيقاً لا يستجيب؟ ينظر إلى النافذة ومعالجة الرسائل لمؤشّر ترابط الواجهة الذي يملكها. الحكم ليس لكلّ عمليّة الفصل 2
لماذا تتوقّف الأزرار وإعادة الرسم؟ مؤشّر ترابط الواجهة لا يستطيع العودة من معالج حدث أو انتظار، لذا لا يستخرج الرسالة التالية الفصلان 3 و4
لا أريد أن تتجمّد الشاشة أثناء عمليّة طويلة انقل عمل المعالج وواجهات البرمجة المتزامنة فقط إلى مؤشّر عامل؛ استخدم واجهات غير متزامنة للإدخال/الإخراج الفصل 5
هل يكفي تجنّب العرض بـ DoEvents أو إعداد؟ ذلك يستدعي أخطاء إعادة دخول أو يخفي العرض فحسب. ليس إصلاحاً جذريّاً الفصل 6
أريد معرفة سبب التجمّد أحياناً خذ تفريغاً عند لحظة التعليق، قبل الخروج أو إعادة التشغيل الفصل 7

مبدأ الإصلاح لا تنتظر طويلاً ولا تحسب حساباً ثقيلاً على مؤشّر ترابط الواجهة. يتولّى مؤشّر ترابط الواجهة الإدخال، والرسم، وعرض التقدّم، وقبول الإلغاء، ويُفصَل عن العمل المستغرق للوقت.3

كذلك، الزمن حتّى يظهر «لا يستجيب» وزمن الاستجابة الذي يشعر بالراحة شيئان مختلفان. البقاء دون 5 ثوانٍ ليس كافياً. يشرح القسم 4.5 هذا الفرق.

2. «لا يستجيب» حكم نظام التشغيل: قاعدة الثواني الخمس والشاشة البديلة

2.1 وحدة الحكم هي النافذة ومؤشّر الترابط المالك، لا العمليّة كلّها

تعامل وثائق Microsoft لـ IsHungAppWindow النافذة غير مستجيبة عندما تستوفي الشروط التالية.1

الشرط المعنى
ليس في انتظار إدخال النافذة ليست في حالة انتظار إدخال
ليس في تسلسل البدء التطبيق ليس في تسلسل بدئه
لا يستخرج رسائل لم يستدعِ PeekMessage لمدّة المهلة الداخليّة البالغة 5 ثوانٍ

نظام التشغيل لا ينظر إلى ما يحسبه التطبيق؛ ينظر إلى ما إذا كانت حلقة الرسائل تدور. حلقة الرسائل هي الآليّة التي تستخرج طلبات الإدخال وإعادة الرسم بالترتيب وتعالجها. يشرح الفصل 3 كيف تعمل فعلاً.

وتنصّ الوثائق نفسها أيضاً أنّ قيمة الـ 5 ثوانٍ قد تتغيّر في المستقبل. إنّها المهلة الداخليّة لحكم عدم الاستجابة، لا قاعدة تصميم تقول «يجوز لك حجب الواجهة حتّى 5 ثوانٍ».1

الحكم ليس لكلّ عمليّة. في تطبيق يملك عدّة مؤشّرات ترابط واجهة، قد تتجمّد نافذة بينما نوافذ يملكها مؤشّرات أخرى تظلّ تعمل. في التحقيق أيضاً، يجب أن تتجاوز النظر إلى العمليّة وتحدِّد مؤشّر الترابط الذي يملك النافذة المعلَّقة.

2.2 الشاشة البيضاء المتجمِّدة «نافذة شبح»

عندما تُحكَم نافذة عليا غير مستجيبة، يخفي Windows النافذة الأصليّة ويستبدلها بنافذة شبح لها ترتيب Z والموضع والحجم والمظهر نفسه. نصّ «(لا يستجيب)» في العنوان والمظهر الأبيض المتجمِّد تحت سمة Aero يأتيان من هذه الشاشة البديلة، لا من التطبيق المعلَّق.2

حالة النافذة ما يراه المستخدم
نافذة التطبيق الأصليّة لا تستطيع معالجة الرسائل؛ لا تتفاعل مع النقرات أو إعادة الرسم
نافذة الشبح التي يوفّرها نظام التشغيل عمليّات محدودة كالنقل وتغيير الحجم والتصغير والإغلاق

القدرة على تحريك النافذة البديلة لا تعني أنّ محتويات التطبيق بدأت تعمل من جديد. نظام التشغيل ينوب فقط عن الحدّ الأدنى من العمليّات.24

شكوى أنّ «العرض يظهر أبكر ممّا ينبغي» ينبغي أيضاً أن تُعامَل أوّلاً مشكلة أنّ مؤشّر ترابط الواجهة لا يعود إلى معالجة الرسائل لمدّة طويلة. التحقيق في المعالجة المتوقّفة يأتي قبل كبح العرض.

2.3 عدم رؤيته تحت المصحِّح لا يعني أنّه ليس معلَّقاً

بينما مصحِّح مرفق، لا يُنشئ نظام التشغيل نوافذ شبح. لذا حتّى عندما يكون ثمّة فرق مثل «لا يظهر عند التشغيل تحت المصحِّح، لكنّه يصير «لا يستجيب» في التشغيل العاديّ»، قد يكون مؤشّر ترابط الواجهة محجوباً بالطريقة نفسها تماماً، والفرق في العرض وحده.2

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

3. لماذا تتوقّف الشاشة: مؤشّر ترابط الواجهة وحلقة الرسائل

3.1 الإدخال وإعادة الرسم يعالجهما المؤشّر نفسه

تطبيقات واجهة Windows مدفوعة بالأحداث. تستقبل رسائل للفأرة ولوحة المفاتيح وطلبات إعادة الرسم والمؤقِّتات وما شابه، وتشغِّل المعالجة التي تقابل كلاً منها. لكلّ مؤشّر ترابط يُنشئ نافذة طابور رسائل ويشغِّل حلقة كهذه.5

MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);   // يُستدعى إجراء النافذة
}

GetMessage يستخرج رسالة من الطابور، وDispatchMessage يستدعي إجراء النافذة لتلك النافذة. إجراء النافذة هو الدالّة التي تؤدّي معالجة الرسالة. معالجة نقرة الزرّ، وإعادة الرسم، ومعالجات أحداث WinForms وWPF كلّها تعود إلى معالجة الرسائل هذه على مؤشّر ترابط الواجهة.6

البنية الأساسيّة لحلقة الرسائليضع نظام التشغيل إدخال الفأرة ولوحة المفاتيح وغيرهما في طابور رسائل المؤشّر؛ تستخرجه حلقة مؤشّر الواجهة بـ GetMessage وتستدعي إجراء النافذة بـ DispatchMessage، ومتى انتهت المعالجة تعود إلى رأس الحلقةنظام التشغيل (إدخال، طلبات إعادة رسم، مؤقِّتات)طابور رسائل المؤشّرالاستخراج بـ GetMessageDispatchMessageالمعالجة في إجراء النافذة

الشكل 1: استخرج رسالة، عالجها في إجراء النافذة، وعد إلى الحلقة. هذه الدورة هي ما يبقي الإدخال والرسم يعملان.

الآن، ماذا يحدث عندما تُستدعى عمليّة طويلة من داخل معالج نقرة؟ إلى أن تعود تلك العمليّة، لا يستطيع مؤشّر ترابط الواجهة استخراج الرسالة التالية. النقرات وطلبات إعادة الرسم التي تصل بعدها لم يعد ممكناً معالجتها. هذه البنية الأساسيّة لـ «شاشة متجمِّدة».

3.2 PostMessage وSendMessage يعودان في وقتين مختلفين

لتسليم الرسائل مساران: واحد يضع الرسالة على الطابور، وواحد ينتظر اكتمال المعالجة.57

الواجهة السلوك متى يعود المستدعي
PostMessage يضع الرسالة على الطابور؛ يستخرجها المستقبل ويعالجها يعود متى وُضعت الرسالة في الطابور. لا ينتظر انتهاء المعالجة
SendMessage يرسل الرسالة إلى إجراء النافذة ويجعله يعالجها لا يعود حتّى ينتهي إجراء النافذة من المعالجة

على الخصوص، عندما يُرسَل SendMessage إلى نافذة على مؤشّر آخر، يُجعَل المرسل أيضاً ينتظر حتّى يستطيع المستقبل معالجة الرسالة. هذا الفرق يؤدّي مباشرةً إلى الجمود في القسم 4.3.7

4. الأسباب حسب الفئة: خمسة أنماط تحجب مؤشّر ترابط الواجهة

عرض «لا يستجيب» يبدو واحداً، لكنّ ما يحجب يختلف. افصل المرشّحين أوّلاً، ثمّ أكِّد بتفريغات وتتبعات الفصل 7.

مرشّح السبب ما يحدث على مؤشّر ترابط الواجهة المظهر النموذجيّ
إدخال/إخراج متزامن أو استدعاء شبكة انتظار اكتمال ملفّ أو قاعدة بيانات أو Web API أو ما شابه سريع على جهاز التطوير، لكن يتجمّد في الإنتاج أو في بيئات معيّنة
انتظار قفل لا يستطيع أخذ قفل يمسكه مؤشّر عامل فينتظر يتجمّد نادراً، بحسب تداخل العمليّات
SendMessage عبر مؤشّرات الترابط انتظار أن يعالج مؤشّر آخر الرسالة يُسحَب إلى انتظار متبادل أو بثّ
استدعاء إلى STA في COM انتظار معالجة رسائل الـ STA المستهدَف مؤشّر واجهة متوقّف يمتدّ إلى استدعاءات COM من مؤشّرات أخرى
تراكم عمليّات قصيرة كلّ واحدة قصيرة، لكنّ التنفيذ المتتابع لا يعود إلى الحلقة أبداً تصير العمليّات بطيئة فقط عندما ينمو عدد العناصر

4.1 الإدخال/الإخراج المتزامن واستدعاءات الشبكة

النمط الأكثر شيوعاً هو الأداء بشكل متزامن، داخل معالج نقرة زرّ، لقراءة أو كتابة ملفّ كبير، أو استعلام قاعدة بيانات، أو استدعاء Web API، أو الوصول إلى محرّك شبكة.

ما ينتهي في جزء من الثانية على جهاز التطوير يمكن أن يصير انتظاراً لعشرات الثواني تحت تأخير شبكة الإنتاج أو مشكلة في خادم الملفّات. لمحرّكات الشبكة مهلات طويلة عندما ينقطع الاتّصال، ممّا يزيد العَرَض سوءاً. «كان سريعاً على جهاز التطوير» ليس سبباً للانتظار على مؤشّر ترابط الواجهة.

4.2 مؤشّر ترابط الواجهة ينتظر قفلاً يمسكه مؤشّر عامل

الأقفال التي تحمي البيانات المشتركة يمكنها أيضاً إيقاف الواجهة. عندما يمسك مؤشّر عامل قفلاً لمدّة طويلة ويحاول مؤشّر الواجهة أيضاً أخذ ذلك القفل، ينتهي مؤشّر الواجهة منتظراً حتّى يستطيع أخذه.

حتّى بعد نقل العمل إلى مؤشّر عامل، الشاشة لا تُفرَّغ إن انتظر مؤشّر الواجهة انتهاء ذلك العمل أو تحرير القفل. انضباط الأقفال مشروح بالتفصيل في سلسلة تعدّد مؤشّرات الترابط العمليّة.

4.3 انتظار اكتمال بعضهما بعضاً عبر SendMessage

الجمود الكلاسيكيّ هو التركيب الذي فيه ينتظر مؤشّر الواجهة انتهاء مؤشّر عامل، ويرسل ذلك العامل SendMessage إلى الواجهة وينتظر. الواجهة تنتظر ولا تستطيع معالجة الرسائل، والعامل لا يستطيع العودة من SendMessage، لذا لا يستطيع أيّ منهما التقدّم.75

جمود من SendMessage عبر مؤشّرات الترابطعندما يرسل مؤشّر عامل SendMessage إلى نافذة مؤشّر الواجهة بينما مؤشّر الواجهة محجوب ينتظر نتيجة العامل، ينتظر كلّ منهما انتهاء الآخر وتكون النتيجة جموداًمؤشّر ترابط عاملمؤشّر ترابط الواجهةمؤشّر ترابط عاملمؤشّر ترابط الواجهةلا يستطيع معالجة الرسائل (ينتظر)لا يستطيع العودة من SendMessageانتظار متبادل: جمودانتظار انتهاء العامل (محجوب)SendMessage (لا يعود حتّى تُعالَج)

الشكل 2: نقل العمل إلى مؤشّر عامل وحده لا يكفي. عندما تنتظر الواجهة والعامل اكتمال بعضهما بعضاً، النتيجة جمود.

الإرسال إلى HWND_BROADCAST يستدعي الحذر أيضاً. نافذة واحدة غير مستجيبة تكفي لسحب المرسل. حيث لا تستطيع تحمّل الاستمرار في الانتظار، انظر في SendMessageTimeout الذي يضع مهلة، أو PostMessage الذي لا ينتظر اكتمال المعالجة.8

4.4 STA في COM يتوقّف ويسحب مؤشّرات أخرى معه

الاستدعاءات من مؤشّرات أخرى إلى كائن STA تُسلَّم كرسائل نافذة. لذا عندما يُحجَب مؤشّر الواجهة (STA)، تُحجَب استدعاءات COM إليه أيضاً. إنّها بنية ينتهي فيها لا الواجهة فحسب بل المستدعون أيضاً منتظرين.

العلاقة بين الشقق ومعالجة الرسائل مشروحة في مقالة COM STA/MTA.

4.5 تكرار «لحظة واحدة فقط» 100 مرّة

حتّى عمليّة متزامنة بـ 50 مليّ ثانية تبلغ 5 ثوانٍ حين تُكرَّر 100 مرّة. لا تحكم بقياس الدوالّ فرادى وإيجادها قصيرة؛ فكِّر بـ الزمن الإجماليّ حتّى يعود مؤشّر ترابط الواجهة إلى حلقة الرسائل.

البطء المحسوس يبدأ عند نحو 100 مليّ ثانية. لا تستهدف عتبة عدم الاستجابة البالغة 5 ثوانٍ؛ صمِّم على افتراض أنّ مؤشّر الواجهة يجوز حجبه لميليّ ثوانٍ فقط.

5. تصاميم لا تتجمّد: افصل تشغيل العمل عن تحديث الشاشة

5.1 افصل الحساب وواجهات البرمجة المتزامنة عن الإدخال/الإخراج غير المتزامن

مبدأ الإصلاح إخراج العمل المستغرق للوقت من مؤشّر ترابط الواجهة. غير أنّ كلّ شيء لا يُنقَل بالطريقة نفسها.

نوع العمل المعالجة الأساسيّة في C# دور مؤشّر ترابط الواجهة
حساب ثقيل على المعالج انقله إلى مؤشّر عامل بـ Task.Run انتظر الاكتمال بشكل غير متزامن واعرض النتيجة
معالجة لا تملك سوى واجهة متزامنة افصلها إلى مؤشّر عامل لا تحجب الواجهة منتظراً الاكتمال بشكل متزامن
إدخال/إخراج بواجهة غير متزامنة await لـ GetStringAsync أو ما شابه عُد إلى معالجة الرسائل بينما الإدخال/الإخراج معلَّق
الإدخال، والرسم، والتقدّم، والإلغاء عالجها على مؤشّر الواجهة لا تخلط حساباً طويلاً أو إدخالاً/إخراجاً متزامناً

مع الإدخال/الإخراج غير المتزامن لا حاجة لاحتلال مؤشّر فقط للانتظار. عيّنة الشيفرة أدناه أيضاً تستخدم Task.Run للحساب والواجهة غير المتزامنة الأصليّة لاتّصال HTTP.3

5.2 في C#، استخدم async/await للعودة إلى الواجهة وتحديثها

المثال التالي يفصل العمل الثقيل عن تحديثات الواجهة في معالج نقرة WinForms. لأنّ هذا المثال يبدأ من معالج حدث واجهة ويحفظ سياق تنفيذ الواجهة، الاستمرار بعد await يعود إلى مؤشّر ترابط الواجهة، حيث يمكنه تحديث عناصر التحكّم.3

private async void RunButton_Click(object sender, EventArgs e)
{
    runButton.Enabled = false;
    try
    {
        // العمل الثقيل على CPU وواجهات API التي لا تتوفّر إلا بشكل متزامن تذهب إلى عامل عبر Task.Run
        var result = await Task.Run(() => HeavyCalculation(input));

        // للإدخال/الإخراج، استخدم API غير متزامنة أصليّاً (لا تستهلك مؤشّر ترابط أيضاً)
        var data = await httpClient.GetStringAsync(url);

        // بعد await نعود إلى مؤشّر الواجهة، لذا يمكن لمس عناصر التحكّم مباشرةً
        resultLabel.Text = result + data.Length.ToString();
    }
    catch (Exception ex)
    {
        // استثناء يتسرّب من معالج async void يُسقط التطبيق. التقطه هنا
        MessageBox.Show($"فشلت العمليّة: {ex.Message}");
    }
    finally
    {
        runButton.Enabled = true;
    }
}

ثمّة أربع نقاط تُؤخَذ منه.

الموضع القصد
انتظار الاكتمال بـ await يعيد مؤشّر الواجهة إلى حلقة الرسائل كلّما حدث انتظار
تحديث الشاشة بعد await لا يلمس عناصر التحكّم من العامل
تعطيل الزرّ أثناء التشغيل يمنع بدء العمليّة نفسها مراراً
catch وfinally يمنع تسرّب الاستثناءات من معالج async void ويعيد حالة الزرّ

هذه عيّنة شيفرة تُظهر تقسيم الأدوار؛ تنفيذ HeavyCalculation وما شابه، ومعالجة إغلاق النموذج، والتقدّم والإلغاء محذوفة. التقدّم والإلغاء اللذان يحتاجهما العمل الطويل يُضافان على حدة، كما في القسم 5.4.

عناصر تحكّم WinForms وعناصر WPF تُعالَج من مؤشّر الترابط الذي أنشأها. لمسها مباشرةً من عامل يؤدّي إلى استثناءات أو سلوك غير معرَّف. للعودة إلى مؤشّر الواجهة صراحةً، استخدم Control.Invoke / BeginInvoke في WinForms وDispatcher.InvokeAsync في WPF.3

5.3 في Win32، أبلغ عن الاكتمال بـ PostMessage

تقسيم الأدوار هو نفسه في Win32 الأصليّ. سلِّم العمل إلى مؤشّر عامل، ومتى انتهى أرسل رسالة اكتمال مخصَّصة بـ PostMessage وحدِّث الشاشة في إجراء نافذة مؤشّر الواجهة. لأنّ PostMessage يعود متى وُضعت الرسالة في الطابور، لا ينتظر العامل انتهاء الواجهة من المعالجة.7

تقسيم الأدوار في تطبيق لا يتجمّدمؤشّر الواجهة يتولّى فقط قبول الإدخال وعرض التقدّم وقبول الإلغاء؛ مؤشّر عامل يشغِّل العمل الثقيل ويعيد الاكتمال إلى مؤشّر الواجهة عبر PostMessage أو استمرار awaitتسليم العملPostMessage / استمرار awaitمؤشّر الواجهة: إدخال، تقدّم، إلغاءمؤشّر عامل: عمل ثقيللا إدخال/إخراج متزامن ولا حساب طويل على مؤشّر الواجهة

الشكل 3: سلِّم الحساب الثقيل والعمل المتزامن إلى مؤشّر عامل، وأعد تحديثات الشاشة إلى مؤشّر الواجهة. الإدخال/الإخراج غير المتزامن يُنتظَر بواجهة غير متزامنة، كما في القسم 5.1.

انتظار العامل نفسه يُكتَب وفق الانضباط المشروح في مقالة متغيّر الشرط. مهمّ أيضاً ألّا يعود مؤشّر الواجهة إلى انتظار العامل بشكل متزامن.

5.4 صمِّم التقدّم والإلغاء من البداية

حتّى إن لم تتجمّد الشاشة، إن لم يتغيّر شيء لمدّة طويلة لا يستطيع المستخدم معرفة ما إذا كان يعمل. لذا صمِّم عرض التقدّم وقبول الإلغاء جزءاً من المعالجة.

الاتّجاه الآليّة الدور
العامل ← الواجهة IProgress<T> يبلِّغ التقدّم إلى الشاشة
الواجهة ← العامل CancellationToken ينقل طلب التوقّف

يتوقّف العامل عند حدّ مناسب وينظِّف. وجود وسيلة للتقدّم والإلغاء يقلِّل المواقف التي يلجأ فيها المستخدمون إلى الإنهاء القسريّ. الإنهاء القسريّ يمكن أن يؤدّي إلى فساد البيانات.

6. طريقتان تبدوان حلّين التفافيّين، وحدودهما

6.1 DoEvents يستدعي أحداثاً أخرى إلى وسط العمل

إدخال Application.DoEvents() أو حلقة PeekMessage أثناء عمل ثقيل يعالج الرسائل ويتفادى عرض «لا يستجيب». غير أنّ معالج حدث آخر يعمل الآن بينما العمل الأصليّ لم ينتهِ بعد. هذه إعادة الدخول.

مثلاً، يُستدعى DoEvents في منتصف تحويل بيانات، وتُعالَج نقرة زرّ ثانية كانت في الطابور. يعدِّل ذلك المعالج البيانات نفسها، ثمّ يستأنف العمل الأصليّ. بهذا الترتيب، الحالة التي افترضها العمل الأصليّ قد ضاعت أصلاً.

الأزرار ليست الشيء الوحيد الذي يعيد الدخول. إغلاق النموذج والمؤقِّتات تدخل أيضاً. أخطاء مثل بيانات قيد المعالجة تفسد، أو استثناء من لمس نموذج مغلق، تعتمد على التوقيت ويصعب إعادة إنتاجها، وغالباً أصعب تحقيقاً من «لا يستجيب».

المبدأ فصل العمل، لا ضخّ الحلقة يدوياً. أبقِ معالجة الرسائل اليدويّة محصورة في بنى محدودة كحوار تقدّم نمطيّ. للعمل الطويل العاديّ، استخدم الفصل ومنع إعادة الدخول في الفصل 5.

6.2 تعطيل نوافذ الشبح لا يعيد التحكّم

DisableProcessWindowsGhosting واجهة برمجة تعطِّل الاستبدال بنافذة الشبح للعمليّة المستدعية. التعطيل يستمرّ لعمر العمليّة.4

توجد لاستخدامات خاصّة كأجهزة الأكشاك، حيث تريد منع نظام التشغيل من وضع نافذة بديلة قابلة للتشغيل. واقع توقّف معالجة الرسائل لا يتغيّر، ويخسر المستخدم أيضاً وسيلة النقل أو الإغلاق التي وفّرتها نافذة الشبح. ليست شيئاً يُستخدم إجراءً مضادّاً لـ «لا يستجيب» في التطبيقات العاديّة.

الطريقة ما يتغيّر ما يبقى
الضخّ يدوياً بـ DoEvents أو ما شابه تُعالَج رسائل أخرى في وسط العمل أحداث اعتباطيّة تعيد الدخول ويمكن أن تفسد الحالة
DisableProcessWindowsGhosting يوقف استبدال نظام التشغيل بنافذة بديلة مؤشّر الواجهة يبقى محجوباً
فصل العمل المستغرق للوقت عن الواجهة يتيح لمؤشّر الواجهة العودة إلى الإدخال والرسم يجب أيضاً تصميم التقدّم والإلغاء ومنع إعادة الدخول

7. إجراء التحقيق: احفظ لحظة التعليق قبل الإغلاق

7.1 خذ تفريغاً أوّلاً

في تحقيق «يتجمّد أحياناً»، أغلى شيء هو حالة مؤشّرات الترابط عند لحظة التعليق. متى خرجت أو أعدت التشغيل، تلك الحالة تُفقَد.

في علامة تبويب التفاصيل في مدير المهام، انقر بالزرّ الأيمن العمليّة المستهدفة واختر إنشاء ملفّ تفريغ. يعطيك ذلك تفريغاً كاملاً يحوي مكدّس كلّ مؤشّر. شارك الإجراء أيضاً مع من يستقبل التذاكر: «عندما يتجمّد، خذ تفريغاً قبل إغلاقه».

لبناء آليّة جمع، انظر مقالة جمع تفريغ الانهيار.

7.2 انظر إلى مؤشّر الترابط الذي يملك النافذة المعلَّقة

افتح التفريغ في WinDbg وافحص مكدّس مؤشّر الواجهة. المؤشّر الذي يشغِّل حلقة الرسائل عادةً هو المؤشّر 0، لكن بعض التطبيقات تملك عدّة مؤشّرات واجهة، لذا لا تقرِّر بالرقم وحده؛ انظر إلى المؤشّر الذي يملك النافذة المعلَّقة.

ما يظهر على المكدّس ما تفحصه بعد ذلك
انتظار في ReadFile أو واجهة شبكة وصول ملفّات، إدخال/إخراج متزامن، انتظار ردّ شبكة
انتظار في دالّة WaitFor… القفل أو كائن المزامنة. أيّ اكتمال ينتظر، وماذا يفعل الطرف الآخر
انتظار داخل SendMessage ما إذا كان مؤشّر الوجهة يستطيع معالجة الرسائل. ما إذا كانا ينتظران بعضهما بعضاً

مكدّس مؤشّر الواجهة يُظهر ما ينتظره. عندما يُشتبَه في جمود، طابق أهداف انتظار كلا المؤشّرين، لا واحداً فقط. كيفيّة قراءتها مشروحة في المقالة التمهيديّة لـ WinDbg.

الإجراء الأساسيّ لتحقيق «لا يستجيب»خذ تفريغاً عند لحظة التعليق، انظر إلى مكدّس مؤشّر الواجهة، حدِّد ما إذا كان متوقّفاً على إدخال/إخراج متزامن أو انتظار قفل أو SendMessage عبر مؤشّرات، وصِل ذلك بالإصلاح التصميميّ المقابللحظة التعليقخذ تفريغاً (قبل الإغلاق)انظر إلى مكدّس مؤشّر الواجهةإدخال/إخراج متزامن أو انتظار شبكةانتظار قفلSendMessage عبر مؤشّراتافصل الموضع إلى مؤشّر عامل

الشكل 4: من تفريغ لحظة التعليق، تتبّع ما ينتظره مؤشّر الواجهة. للإدخال/الإخراج، اجعله غير متزامن أو افصله؛ للأقفال وSendMessage، افحص بنية الانتظار أيضاً.

7.3 اختر بين الفحص الحيّ وتحليل الخطّ الزمنيّ

إلى جانب التفريغات، ثمّة طريقة للنظر إلى عمليّة قيد التشغيل في المكان، وطريقة لتسجيل تدفّق الزمن.

ما تريد معرفته الطريقة
حفظ لحظة التعليق وفحصها لاحقاً خذ تفريغاً وحلِّله في WinDbg
رؤية مؤشّرات عمليّة حيّة ومكدّساتها في المكان استخدم Process Explorer
متابعة بطء مستمرّ أو انتظارات مؤشّر الواجهة عبر الزمن التقط تتبّعاً بـ WPR وحلِّله في WPA

كيفيّة التقاط خطّ زمنيّ مشروحة في WPR/WPA عمليّاً.

7.4 ضيِّق المرشّحين من العَرَض، ثمّ أكِّد بالدليل

شروط إعادة الإنتاج أيضاً مدخل للتحقيق. لكن لا تقرِّر السبب من العَرَض وحده؛ قابله بالتفريغات والتتبعات.

العَرَض أوّل ما يُشتبَه به أين تفحص
يتجمّد دائماً على عمليّة معيّنة إدخال/إخراج متزامن أو ما شابه داخل ذلك المعالج المعالجة لتلك العمليّة ومكدّس مؤشّر الواجهة
يتجمّد نادراً، بلا ارتباط بالعمليّات ترتيب الأقفال، أو جمود SendMessage عبر مؤشّرات مكدّسا كلا المؤشّرين المنتظرَين بعضهما بعضاً
يتجمّد فقط في بيئة معيّنة انتظارات ومهلات تسبّبها محرّكات شبكة أو وكلاء أو برمجيّات مكافحة فيروسات وما شابه الواجهة المُنتظَر عليها وزمن استجابتها في تلك البيئة

8. الخلاصة

«لا يستجيب» آليّة يحكم فيها Windows أنّ معالجة رسائل نافذة توقّفت ويضع شاشة بديلة. وحدة الحكم هي النافذة ومؤشّر ترابط الواجهة المالك، لا العمليّة كلّها. مهلة الـ 5 ثوانٍ الداخليّة قيمة قد تتغيّر في المستقبل، وهي متميّزة عن زمن استجابة واجهة مريح.12

ما يُصلَح ليس العرض بل الحساب أو الانتظار الذي يحتلّ مؤشّر ترابط الواجهة لمدّة طويلة. أرسل عمل المعالج وواجهات البرمجة المتزامنة فقط إلى مؤشّر عامل، وأرسل الإدخال/الإخراج غير المتزامن إلى واجهات غير متزامنة، وأعد تحديثات الشاشة إلى مؤشّر الواجهة. صمِّم التقدّم والإلغاء ومنع إعادة الدخول مجموعة واحدة. التهرّب بـ DoEvents أو تعطيل نوافذ الشبح ليسا بديلاً.

عندما يكون السبب مجهولاً، ابدأ بـ أخذ تفريغ قبل الخروج والنظر إلى ما ينتظره مؤشّر الترابط الذي يملك النافذة المعلَّقة. استبدل «يبدو معطوباً» بـ «مؤشّر ترابط الواجهة لا يستطيع العودة إلى معالجة الرسائل»، فتتّصل أماكن الفحص وإصلاحات التصميم.

مقالات ذات صلة

مجالات الاستشارة ذات الصلة

تتعامل شركة كومورا سوفت ذ.م.م. مع تحقيقات السبب الجذريّ (تحليل التفريغ وتحليل التتبّع) لتطبيقات أعمال «تتجمّد أحياناً» أو تصير «لا يستجيب»، وإعادة هيكلة شيفرة واجهة تراثيّة مليئة بمعالجة متزامنة إلى فصل async/await ومؤشّر عامل، ومراجعات تصاميم واجهة لا تتجمّد. حتّى قبل أن يُعرَف إجراء إعادة الإنتاج، يمكننا المساعدة بدءاً من تصميم كيفيّة جمع الدليل.

روابط مرجعيّة

  1. Microsoft Learn, IsHungAppWindow function (winuser.h). حول المعيار أنّ تطبيقاً يُعتبَر غير مستجيب عندما يكون «لا ينتظر إدخالاً، وليس في تسلسل بدئه، ولم يستدعِ PeekMessage لمدّة المهلة الداخليّة البالغة 5 ثوانٍ»؛ وحول إمكان تغيّر معيار الثواني الخمس هذا؛ وحول إرجاع الدالّة TRUE دائماً لنافذة شبح. ↩ ↩2 ↩3 ↩4

  2. Microsoft Learn, GetMessage function (winuser.h). حول معاملة النظام نافذة عليا غير مستجيبة عندما تتوقّف عن الاستجابة للرسائل لعدّة ثوانٍ واستبدالها بنافذة شبح بترتيب Z والموضع والحجم والمظهر نفسه؛ وحول قدرة المستخدم فقط على النقل أو تغيير الحجم أو الإغلاق؛ وحول عدم إنشاء نافذة شبح بينما مصحِّح مرفق. ↩ ↩2 ↩3 ↩4 ↩5

  3. Microsoft Learn, How to make thread-safe calls to controls (Windows Forms). حول عدم أمان لمس عناصر تحكّم WinForms من أيّ مؤشّر غير الذي أنشأها؛ وحول استخدام Invoke/BeginInvoke للتحديثات من مؤشّر آخر؛ وحول أنماط غير متزامنة آمنة باستخدام async/await أو BackgroundWorker. ↩ ↩2 ↩3 ↩4

  4. Microsoft Learn, DisableProcessWindowsGhosting function (winuser.h). حول إمكان تعطيل، لعمليّة الواجهة المستدعية، ميزة نافذة الشبح التي تجعل نافذة غير مستجيبة قابلة للتصغير والنقل والإغلاق؛ وحول استمرار التعطيل لعمر العمليّة. ↩ ↩2

  5. Microsoft Learn, About Messages and Message Queues. حول كون تطبيقات Windows مدفوعة بالأحداث ومعالجة إجراء النافذة للرسائل؛ وحول التمييز بين الرسائل في الطابور والرسائل المُرسَلة مباشرةً؛ وحول استبدال نافذة غير مستجيبة بنافذة شبح؛ وحول القسم الذي يغطّي الجمود من إرسال مؤشّرات الترابط رسائل بعضها لبعض. ↩ ↩2 ↩3

  6. Microsoft Learn, Using Messages and Message Queues. حول تنفيذ نموذجيّ لحلقة رسائل بـ GetMessage وTranslateMessage وDispatchMessage، وحول كيفيّة فحص طابور رسائل. ↩

  7. Microsoft Learn, SendMessage function (winuser.h). حول استدعاء SendMessage إجراء النافذة المحدَّدة وعدم العودة حتّى تكتمل المعالجة؛ وحول جعل إرسال إلى نافذة على مؤشّر آخر المرسِل ينتظر حتّى يعالج ذلك المؤشّر الرسالة؛ وحول الفرق عن PostMessage، الذي يضع الرسالة على الطابور دون انتظار ردّ. ↩ ↩2 ↩3 ↩4

  8. Microsoft Learn, SendMessageTimeout function (winuser.h). حول إمكان إرسال رسالة بمهلة؛ وحول علم (SMTO_ABORTIFHUNG) يعود دون انتظار عندما تكون النافذة غير مستجيبة (حُكِم بتعليقها). ↩

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

الأسئلة الشائعة

أسئلة شائعة حول موضوع هذه المقالة.

تحت أيّ شروط يظهر «لا يستجيب»؟
يحكم نظام التشغيل أنّ نافذة لا تستجيب عندما يكون تطبيق ذو نافذة لا ينتظر إدخالاً، وليس في تسلسل بدئه، ولم يستخرج رسالة (PeekMessage) لمدّة 5 ثوانٍ. تُخفى النافذة العليا المحكوم عليها وتُستبدَل بـ«نافذة شبح» بالموضع والحجم والمظهر نفسه. نصّ شريط العنوان «(لا يستجيب)» والمظهر الأبيض المتجمِّد ينتميان إلى نافذة الشبح هذه، التي لا تتيح سوى النقل أو التصغير أو الإغلاق. بمعنى آخر، «لا يستجيب» ليس شيئاً يعرضه التطبيق نفسه؛ إنّه شاشة يضعها نظام التشغيل نيابةً عن التطبيق.
هل ثمّة إعداد يمنع ظهور «لا يستجيب» أثناء سير العمل؟
استدعاء DisableProcessWindowsGhosting يعطِّل الاستبدال بنافذة شبح لتلك العمليّة. غير أنّ ذلك يجعل التعليق أقلّ ظهوراً للمستخدم فحسب: النافذة ما زالت لا تتفاعل مع الإدخال، ومن منظور المستخدم تجمّد تامّ بلا سبيل للنقل أو الإغلاق. الإصلاح الحقيقيّ ليس كبح العرض، بل نقل العمل الثقيل إلى مؤشّر ترابط عامل حتّى لا يُحجَب مؤشّر ترابط الواجهة ولو لجزء من الثانية، فضلاً عن خمس ثوانٍ. لاحظ أيضاً أنّ نظام التشغيل لا يُنشئ نافذة شبح بينما مصحِّح مرفق، لذا قد يبدو كأنّ «لا يستجيب» لا يحدث أبداً أثناء التصحيح.
هل يُقبَل تجنّب «لا يستجيب» بـ DoEvents (ضخّ حلقة الرسائل يدوياً)؟
غير مستحسَن. تدوير DoEvents أو حلقة PeekMessage في وسط عمل ثقيل يتفادى حكم عدم الاستجابة، لكن أيّ معالج حدث يمكنه حينها إعادة الدخول في منتصف العمل: نقرة ثانية على الزرّ، وإغلاق النافذة، ومؤقِّت، وما شابه. أخطاء إعادة الدخول مثل معالج آخر يعيد كتابة بيانات ما زالت تُعالَج، أو يلمس نموذجاً كان يُفترَض إغلاقه ويرمي استثناءً، تعتمد على التوقيت ويصعب إعادة إنتاجها، وهي أخطر من «لا يستجيب» نفسه. النهج السليم نقل العمل نفسه إلى مؤشّر ترابط عامل بـ Task.Run أو ما شابه، وترك مؤشّر ترابط الواجهة مسؤولاً فقط عن عرض التقدّم وقبول الإلغاء.
كيف أحدِّث الشاشة (عناصر التحكّم) من مؤشّر ترابط عامل؟
يجوز لمس عناصر تحكّم WinForms وعناصر WPF فقط من مؤشّر الترابط الذي أنشأها (عادةً مؤشّر ترابط الواجهة). لمسها مباشرةً من مؤشّر عامل يسبّب استثناءات أو سلوكاً غير معرَّف. في C#، async/await أقصر طريق: الاستمرار بعد await يعود إلى مؤشّر ترابط الواجهة المستدعي، لذا يمكنك تحديث العناصر بشكل عاديّ بعد الـ await. للتبديل صراحةً، استخدم Control.Invoke/BeginInvoke في WinForms وDispatcher.InvokeAsync في WPF. في Win32 الأصليّ، النمط المستقرّ أن يُرسل مؤشّر العامل PostMessage برسالة اكتمال مخصَّصة إلى مؤشّر الواجهة، وأن يحدِّث إجراء النافذة الشاشة.
كيف أحقّق في سبب عرض تطبيق «لا يستجيب»؟
المهمّ التقاط الحالة عند «اللحظة نفسها» للتعليق. خذ أوّلاً تفريغاً كاملاً من علامة تبويب التفاصيل في مدير المهام بـ «إنشاء ملفّ تفريغ»، ثمّ في WinDbg انظر إلى مكدّس مؤشّر ترابط الواجهة (المؤشّر الذي يشغِّل حلقة الرسائل). ما إذا كان عالقاً في إدخال/إخراج متزامن، أو انتظار شبكة، أو انتظار قفل، أو انتظار مؤشّر آخر عبر SendMessage يظهر على المكدّس كما هو. للنظر إلى عمليّة حيّة، قائمة مؤشّرات الترابط وعرض المكدّس في Process Explorer مفيدان؛ لمتابعته عبر الزمن، التقاط تتبّع WPR فعّال. انظر أيضاً مقالات WinDbg التمهيديّة، وProcess Explorer عمليّاً، وWPR/WPA عمليّاً في هذا الموقع.

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.

العودة إلى المدونة