إغلاق Windows كما يراه تطبيقك ── النجاة من إشعارات الخروج وإعادة التشغيل وفقدان الطاقة بشكل صحيح

· · Windows, الإغلاق, تطوير Windows, خدمات Windows, حواسيب الأجهزة, سلامة البيانات, تشغيل طويل الأمد, UPS

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

ما يشترك فيه الموقعان هو معاملة الإغلاق «حدثاً شاذاً ينبغي ألا يحدث». في الواقع، غير أنّه من إعادة تشغيل Windows Update التلقائيّة، وخروج المستخدم، وإغلاق يبدأه UPS، حتّى فقدان طاقة بلا إعلان، أحداث تقطع التنفيذ من خارج التطبيق ستأتي، عاجلاً أم آجلاً. لا يمكنك منعها من المجيء. ما يمكنك منعه هو «فقدان البيانات عندما تأتي».

لحسن الحظّ، لدى Windows آليّة تُشعر التطبيق قبل الإغلاق، لتطبيقات الواجهة والتطبيقات الطرفيّة والخدمات على حدّ سواء. موجَّه إلى موظّفي تقنيّة المعلومات في المنشآت الصغيرة والمتوسّطة ومطوِّري تطبيقات Windows (خصوصاً تطبيقات حواسيب الأجهزة والتشغيل الطويل)، ينظِّم هذا المقال كيفيّة استقبال تلك الإشعارات، وكيفيّة تصميم تنظيف «ينتهي في ثوانٍ»، والاستعادة التلقائيّة بعد إعادة التشغيل، وكيفيّة الاستعداد لفقدان طاقة لا يأتي بإشعار ── كلّها مستندة إلى مصادر Microsoft Learn الأوّليّة حتّى أغسطس 2026.

1. الخلاصة أوّلاً

  • صمِّم الإغلاق «حدثاً عاديّاً سيأتي، عاجلاً أم آجلاً». الزمن الذي يمكنك استخدامه بعد استقبال الإشعار، من حيث المبدأ، نحو 5 ثوانٍ فقط، لذا تصميم يحفظ كلّ شيء بلهفة في المكان سينهار. الشرط المسبق حفظ تلقائيّ متكرّر بحيث يبقى «الفرق الذي يجب حفظه عند الإغلاق» صغيراً.1
  • على أنظمة تشغيل العميل من Windows 8 فصاعداً، عندما يكون بدء التشغيل السريع مفعّلاً (الافتراضيّ على معظم الحواسيب التي تدعم الإسبات)، «إيقاف التشغيل» إغلاق هجين، والنواة تُسبِت فحسب. الشيء الوحيد الذي يُصفَّر بالكامل هو «إعادة التشغيل». ذلك السبب الحقيقيّ لـ «أوقفته ولم يتحسّن، ثمّ أعدت التشغيل فتحسّن».2
  • تطبيق واجهة ينبغي أن يعيد TRUE فوراً إلى WM_QUERYENDSESSION، وينفّذ التنظيف في WM_ENDSESSION. من حيث المبدأ يجب ألا تعيد FALSE (رفض).1
  • فقط عندما يكون لديك حقّاً عمليّة لا يمكن مقاطعتها ينبغي أن تعرض سبباً بـ ShutdownBlockReasonCreate. حتّى حينها يستطيع المستخدم ونظام التشغيل فرض المتابعة، لذا تصميم يفترض «نستطيع الحظر» لا يصمد.34
  • تطبيق طرفيّ يستقبل الإشعار بـ SetConsoleCtrlHandler. المهلة أقصر بعد ── 5 ثوانٍ افتراضيّاً لإغلاق الطرفيّة. ثمّة فخّ أيضاً: في عمليّة حمّلت gdi32.dll أو user32.dll، بعض هذه الأحداث لا يصل.56
  • تنظيف يعتمد على AppDomain.ProcessExit في .NET لا يعمل، من .NET 10 فصاعداً، على مسارات تُنهى فيها العمليّة «من الخارج». عند خروج عاديّ كالعودة من Main ما زال يعمل كما قبل، لكن لأنّ وقت التشغيل لم يعد يوفّر معالجة افتراضيّة لإشارات الإنهاء كإغلاق الطرفيّة والإغلاق، يجب أن ينتقل التنظيف على تلك المسارات إلى الإشعار الذي يطابق نموذج التطبيق.7
  • خدمة Windows يمكنها استقبال SERVICE_ACCEPT_PRESHUTDOWN أبكر، وبمهلة قابلة للضبط، من SERVICE_ACCEPT_SHUTDOWN (نحو 20 ثانية مهلة). غير أنّ مهلة PRESHUTDOWN الافتراضيّة قُصِّرت إلى 10 ثوانٍ من Windows 10 Creators Update فصاعداً، لذا في الحالين تحتاج تصميماً لا يميل أكثر ممّا ينبغي على المهلة.89
  • الاستعادة التلقائيّة بعد إعادة التشغيل يمكن تحقيقها بالجمع بين RegisterApplicationRestart وARSO (تسجيل دخول تلقائيّ). مسارات استعادة موفَّرة للانهيار، وعدم الاستجابة، وإعادة تشغيل مدفوعة بتحديث.1011
  • فقدان الطاقة لا يأتي بأيّ إشعار أصلاً. النمط القياسيّ الكتابة بالكامل إلى ملفّ مؤقّت، والإفراغ، والتبديل بـ ReplaceFile، لكن لأنّ ReplaceFile لا يضمن الذرّيّة عبر فقدان طاقة أيضاً، مسار استعادة من نسخة احتياطيّة (.bak) زائد تحقّق وقت التحميل جزء من المجموعة. العزل بعد الواقعة يمكن من سجلّ الأحداث (1074/41/6008).121314

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

2. ماذا يحدث عند الإغلاق ── أربع طرق للانتهاء

2.1. الخروج، والإغلاق، وإعادة التشغيل، وفقدان الطاقة

من منظور التطبيق، ما يهمّ محوران: «كيف تنتهي جلسة المستخدم» و«ماذا يحدث للنواة».

العمليّة جلسة المستخدم النواة والتعريفات الإشعار إلى التطبيق
الخروج تنتهي تواصل العمل WM_QUERYENDSESSION ‏(ENDSESSION_LOGOFF) ← WM_ENDSESSION
الإغلاق (بدء سريع مفعّل) تنتهي تُسبِت (تُحفَظ في hiberfil.sys) WM_QUERYENDSESSION ← WM_ENDSESSION، ‏(PRE)SHUTDOWN للخدمات
إعادة التشغيل تنتهي تنتهي بالكامل؛ الإقلاع التالي إقلاع كامل كما أعلاه
فقدان الطاقة تختفي فوراً تختفي فوراً لا شيء

الخروج والإغلاق، من منظور التطبيق، حدث شبه واحد. إن كانت بتّة ENDSESSION_LOGOFF مضبوطة في lParam لـ WM_QUERYENDSESSION فهو خروج؛ إن كانت 0 فهو إغلاق أو إعادة تشغيل (لا يمكنك التمييز بينهما).1 بعبارة أخرى، رضا «إنّه خروج فحسب، سنكون بخير» لا يصمد، والتصميم الصحيح أن تُستدعى شيفرة التنظيف نفسها.

أربع طرق للانتهاء، والإشعار إلى التطبيقالخروج والإغلاق وإعادة التشغيل تسلِّم إشعار WM_QUERYENDSESSION إلى WM_ENDSESSION، وينتهي التنظيف في ثوانٍ. فقدان الطاقة وحده بلا إشعار أصلاً، لذا تستعدّ بتصميم الكتابة في الفصل 8 وUPSخروجQUERY ← ENDSESSIONإغلاقإعادة تشغيلفقدان طاقةبلا إشعار: كتابة + UPSتنظيف في ثوانٍ

الشكل 1: الخروج والإغلاق وإعادة التشغيل تسلِّم إشعار WM_QUERYENDSESSION إلى WM_ENDSESSION، وينتهي التنظيف في ثوانٍ. فقدان الطاقة وحده بلا إشعار أصلاً، لذا تستعدّ بتصميم الكتابة في الفصل 8 وUPS.

2.2. السبب الحقيقيّ لـ «أوقفته ولم يتحسّن» ── الإغلاق الهجين

الصفّ السهل تفويته هو الثاني في الجدول. على أنظمة تشغيل العميل من Windows 8 فصاعداً، بدء التشغيل السريع (الإغلاق الهجين) مفعّل افتراضيّاً على الحواسيب التي تدعم الإسبات، وتغيّر سلوك «إيقاف التشغيل». خروج جلسة المستخدم ما زال يحدث كالمعتاد، لكنّ جلسة النواة لا تُغلَق؛ تُحفَظ، والتعريفات معها، في ملفّ الإسبات (hiberfil.sys) وتُستعاد كما هي في الإقلاع التالي. ذلك يجعل البدء أسرع، لكنّ حالة النواة والتعريفات تصمد حتّى بعد قطع الطاقة.2 غير أنّ هذا سلوك مشروط. في بيئة عُطِّل فيها الإسبات نفسه (powercfg /hibernate off)، وحيث أوقفت سياسة أو خيارات الطاقة بدء التشغيل السريع، وعلى Windows Server، الإغلاق إغلاق كامل تقليديّ. يمكنك أن تعلم أيّ طريق يسلكه حاسوب معيّن من مربّع «تشغيل بدء التشغيل السريع» في خيارات الطاقة، أو ممّا إذا كان powercfg /a (حالات السكون المتاحة) يسرد «Fast Startup».

ماذا يحدث للنواة عند عمليّة إيقاف التشغيلعمليّة إيقاف التشغيل تنقسم إلى إغلاق كامل أو إسبات نواة حسب ما إذا كان بدء التشغيل السريع شغّالاً، وإعادة التشغيل تنفّذ دائماً إقلاعاً كاملاًبدء سريع شغّالإسبات معطَّل / خادمإيقاف التشغيلإعادة التشغيلالجلسة تنتهي + إسبات النواةإغلاق كاملالتالي: استعادة النواةالتالي: إقلاع كامل

الشكل 2: عمليّة إيقاف التشغيل تنقسم إلى إغلاق كامل أو إسبات نواة حسب ما إذا كان بدء التشغيل السريع شغّالاً، وإعادة التشغيل تنفّذ دائماً إقلاعاً كاملاً.

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

  • «أوقفته وأعدت الطاقة، لكنّ مشكلة الجهاز لم تختفِ» ── النواة والتعريفات استُعيدت فقط من الإسبات؛ لم تُصفَّر
  • «تحسّن بعد أن أعدت التشغيل» ── لأنّ إقلاعاً كاملاً هيّأها
  • إجراءات حوادث حواسيب الأجهزة ينبغي أن تقول «إعادة التشغيل»، لا «اقطع الطاقة وأعدها»

إن أردت جعل إغلاق كامل صريحاً من سطر الأوامر، shutdown /s (افتراضيّ Shutdown.exe إغلاق كامل)؛ إن أردت السلوك الهجين الافتراضيّ، shutdown /s /hybrid.2 تعطيل بدء التشغيل السريع غير موصى به. جانب التطبيق ينبغي أن يفترض «عند الإغلاق قد تُسبِت النواة فحسب» ── مثلاً، لا تقدِّر «زمن التشغيل التراكميّ» من زمن إقلاع نظام التشغيل ── ويصمِّم بحيث لا ينكسر في الحالين (ما إذا كان بدء التشغيل السريع شغّالاً أو معطَّلاً يختلف حسب البيئة).

3. كيف ينبغي أن يتصرّف تطبيق واجهة ── WM_QUERYENDSESSION وWM_ENDSESSION

3.1. كيف تقسم الرسالتان العمل

تطبيق له نافذة وطابور رسائل يُشعَر بنهاية الجلسة على مرحلتين.1

  1. WM_QUERYENDSESSION ── استعلام: «هل يصحّ الإنهاء؟» ينبغي أن يعيد التطبيق TRUE فوراً؛ استجابة DefWindowProc الافتراضيّة TRUE أيضاً. لا تبدأ التنظيف هنا.
  2. WM_ENDSESSION ‏(wParam=TRUE) ── إشعار ملتزَم: «الجلسة تنتهي حقّاً». التنظيف يحدث هنا.

إعادة FALSE إلى WM_QUERYENDSESSION يمكن أن تجهض الإغلاق، لكنّ التوثيق صريح أنّ «ينبغي أن تعيد TRUE وتحترم قصد المستخدم»، وتطبيق أعاد FALSE ما زال يُعرَض في واجهة ملء الشاشة «تطبيقاً يمنع الإغلاق». التطبيقات الطرفيّة والتطبيقات بلا نافذة مرئيّة لا تستطيع إجهاض الإغلاق أصلاً، وإن لم تستجب خلال 5 ثوانٍ تُنهى تلقائيّاً.14

تدفّق إشعار نهاية الجلسة على مرحلتينإعادة TRUE إلى استعلام WM_QUERYENDSESSION تلتزم بـ WM_ENDSESSION ويجري التنظيف. الرفض بـ FALSE يعرض التطبيق مانعاً للإغلاق، ونحو 5 ثوانٍ بلا ردّ يمكن أن تفرض المتابعةTRUE(القاعدة)FALSE(رفض)بلا ردّ نحو 5 ثوانٍفرض المتابعةإلغاءWM_QUERYENDSESSIONWM_ENDSESSION(ملتزَم)يُعرَض مانعاً للإغلاقيُعامَل معلَّقاًالتنظيف هناخروج العمليّةأُجهِض الإغلاق

الشكل 3: إعادة TRUE إلى استعلام WM_QUERYENDSESSION تلتزم بـ WM_ENDSESSION ويجري التنظيف. الرفض بـ FALSE يعرض التطبيق مانعاً للإغلاق، ونحو 5 ثوانٍ بلا ردّ يمكن أن تفرض المتابعة.

3.2. ماذا يحدث إن لم تستجب ── جدار الـ 5 ثوانٍ

على كلّ من WM_QUERYENDSESSION وWM_ENDSESSION، يمكنك تأخير الاستجابة بـ نحو 5 ثوانٍ. بعد ذلك، يعرض النظام شاشة «هذا التطبيق يمنع الإغلاق»، ويستطيع المستخدم اختيار المتابعة القسريّة (= إنهاء التطبيق قسراً).4 عمليّة أُنهيت قسراً لا تُعطى فرصة أخرى لإنهاء حفظها.

نقاط التصميم إذن هاتان.

  • أبقِ التنظيف كمّاً ينتهي خلال 5 ثوانٍ. مايكروسوفت نفسها توصي بحفظ البيانات كثيراً في التشغيل العاديّ بحيث يقلّ ما يجب حفظه عند الإغلاق، وحفظ بيانات غير محفوظة في موقع مؤقّت لاستعادتها في الإطلاق التالي.1
  • لا تعرض حوار تأكيد أثناء الإغلاق. بينما تجلس منتظراً «هل تريد الحفظ؟»، تمرّ الـ 5 ثوانٍ. اسقط بصمت إلى الجانب الآمن (حفظ تلقائيّ).

3.3. التنفيذ في WinForms وWPF

في تطبيق سطح مكتب .NET، تُترجَم هذه الرسائل إلى أحداث إطار العمل. في WinForms، يُرفَع FormClosing، ويخبرك CloseReason ما إذا كان الإغلاق هو السبب.

// WinForms: FormClosing is also raised on shutdown / sign-out
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
    if (e.CloseReason == CloseReason.WindowsShutDown)
    {
        // Do only an idempotent snapshot save. Do not show a dialog.
        // Do not set e.Cancel = true (refuse) either.
        SaveWorkingStateToTempFile();
        return;
    }

    // For ordinary closes such as the user clicking the × button, you may confirm here
}

في WPF، يقابل حدث Application.SessionEnding (سمة XAML ‏SessionEnding، أو تجاوز OnSessionEnding).

كيف تتوافق أحداث WinForms/WPF مع الرسائلمرحلة استعلام WM_QUERYENDSESSION تتوافق مع FormClosing في WinForms وSessionEnding في WPF، وما تفعله هناك حفظ لقطة متكافئ على الأكثر. لا حدث مقابل لـ WM_ENDSESSION الملتزم، لذا استقبله في WndProc أو خطّاف ونفِّذ تنظيفاً يمكن تشغيله فقط بعد الالتزامWM_QUERYENDSESSIONWinForms: FormClosingWPF: SessionEndingلقطة متكافئة فقطWM_ENDSESSIONبلا حدث: خطّاف WndProcتنظيف بعد الالتزام

الشكل 4: مرحلة استعلام WM_QUERYENDSESSION تتوافق مع FormClosing في WinForms وSessionEnding في WPF، وما تفعله هناك حفظ لقطة متكافئ على الأكثر. لا حدث مقابل لـ WM_ENDSESSION الملتزم، لذا استقبله في WndProc أو خطّاف ونفِّذ تنظيفاً يمكن تشغيله فقط بعد الالتزام.

// WPF: App.xaml.cs
protected override void OnSessionEnding(SessionEndingCancelEventArgs e)
{
    base.OnSessionEnding(e);

    // You can distinguish ReasonSessionEnding.Logoff / Shutdown,
    // but the baseline is to run the same snapshot save in either case
    SaveWorkingStateToTempFile();

    // Do not set e.Cancel = true unless you have an exceptional reason
}

ثمّة تحذير واحد هنا. كلّ من FormClosing ‏(CloseReason.WindowsShutDown) وSessionEnding في WPF يقابلان مرحلة الاستعلام (WM_QUERYENDSESSION). إن رفض تطبيق آخر، يُجهَض الإغلاق ويواصل تطبيقك العمل. لذا ما يجوز فعله في هذه الأحداث هو حفظ لقطة متكافئ لا يضرّ إن أُجهِض الإغلاق وينتج النتيجة نفسها مهما تكرّر. إن احتجت «تنظيفاً يجب فعله فقط عندما ننتهي فعلاً» (قطع الاتّصال، إعادة الموارد، وما شابه)، اخطف WM_ENDSESSION الملتزم (wParam=TRUE) مباشرةً في WndProc وافعل ذلك هناك.

على أيّ من المسارين، اطوِ الجسم في دالّة «حفظ لقطة» مشتركة واكتب بيانات استعادة لخروج عاديّ وإغلاق و(إن أمكن) انهيار بالصيغة نفسها، بحيث منطق الاستعادة في الإطلاق التالي مسار واحد. التصميم لترك معلومات حتّى عند انهيار مشمول في «كيف نُبقي سجلّات الأعطال في تطبيقات Windows حتى عند الموت بسبب أخطاء برمجية».

4. إن وجب الحظر حقّاً ── ShutdownBlockReasonCreate

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

تدفّق الحماية بـ ShutdownBlockReasonCreateسجّل سبباً عندما تبدأ العمليّة التي لا يمكن مقاطعتها؛ إن وصل طلب إغلاق أثناء الحماية، يُعرَض السبب ملء الشاشة ويُرفَض WM_QUERYENDSESSION بـ FALSE. يستطيع المستخدم الإلغاء أو فرض المتابعة، ويُمسَح السبب عندما تنتهي العمليّةإلغاءفرض المتابعةابدأ عملاً لا يُقاطَعShutdownBlockReasonCreateشغِّل على خيط عاملأنهِ: Destroyإغلاق أثناء هذااعرض السبب + FALSEخروج العمليّة

الشكل 5: سجّل سبباً عندما تبدأ العمليّة التي لا يمكن مقاطعتها؛ إن وصل طلب إغلاق أثناء الحماية، يُعرَض السبب ملء الشاشة ويُرفَض WM_QUERYENDSESSION بـ FALSE. يستطيع المستخدم الإلغاء أو فرض المتابعة، ويُمسَح السبب عندما تنتهي العمليّة.

[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool ShutdownBlockReasonCreate(IntPtr hWnd, string pwszReason);

[DllImport("user32.dll", SetLastError = true)]
static extern bool ShutdownBlockReasonDestroy(IntPtr hWnd);

// Call from the thread that created the main window (it fails from other threads)
_criticalOperationInProgress = true;
ShutdownBlockReasonCreate(this.Handle, "Writing measurement data to a file");
try
{
    // Run the uninterruptible operation on a worker thread. If you run it
    // synchronously on the UI thread the message pump stops, and the process
    // is force-continued as "Not Responding" before the WM_QUERYENDSESSION
    // refusal code below can run
    await Task.Run(() => WriteMeasurementData());
}
finally
{
    ShutdownBlockReasonDestroy(this.Handle);
    _criticalOperationInProgress = false;
}

// Also, refuse WM_QUERYENDSESSION with FALSE only while protected
protected override void WndProc(ref Message m)
{
    const int WM_QUERYENDSESSION = 0x0011;
    if (m.Msg == WM_QUERYENDSESSION && _criticalOperationInProgress)
    {
        m.Result = IntPtr.Zero;   // Refuse. The registered reason string is shown in the full-screen UI
        return;
    }
    base.WndProc(ref m);
}

سوء الفهم السهل هنا تقسيم الأدوار. كلّ ما يفعله ShutdownBlockReasonCreate هو تسجيل سلسلة سبب؛ لا يوقف الإغلاق نفسه. ما يحبس الإغلاق فعلاً هو معالجتك أنت التي تعيد FALSE إلى WM_QUERYENDSESSION بينما علم حماية مضبوط، كما أعلاه. استخدم الاثنين مجموعةً، وامسح كليهما فوراً عندما تنتهي العمليّة. كذلك، شغِّل العمليّة المحميّة نفسها على خيط عامل وأبقِ خيط الواجهة قادراً على معالجة الرسائل ── آليّة الرفض تعمل فقط متى وصلت الرسالة (وحتّى حينها يستطيع المستخدم ونظام التشغيل فرض المتابعة، لذا تصميم كتابة لا ينكسر «إن لم يتوقّف» ── الفصل 8 ── ما زال مطلوباً).

ثمّة ثلاثة تحذيرات تشغيليّة.

  • أبقِ سلسلة السبب قصيرة ومحدّدة. المستخدم مستعجل ولن يقرأ إلا ثواني. التوثيق نفسه يعطي «Burning a CD» مثالاً مناسباً.3
  • لا تتركه مسجَّلاً طوال عمر التطبيق. «فقط أثناء سير عمليّة لا يمكن مقاطعتها» ما تفترضه الواجهة.
  • لا تصمِّم على افتراض أنّك تستطيع الحظر. يستطيع المستخدم اختيار المتابعة القسريّة، وإغلاق قسريّ (ENDSESSION_CRITICAL) لن ينتظر أصلاً. التوثيق صريح: «Applications should not depend on being able to block shutdown».4

5. كيف ينبغي أن تتصرّف التطبيقات الطرفيّة والعمليّات الخلفيّة

5.1. SetConsoleCtrlHandler ومهلة قصيرة

تطبيق طرفيّ لا يستطيع استقبال رسائل نافذة، لذا تصل إشارات التحكّم إلى دالّة معالج مسجَّلة بـ SetConsoleCtrlHandler. المهلة الافتراضيّة لكلّ إشارة كما يلي.5

الإشارة متى تحدث المهلة الافتراضيّة
CTRL_C_EVENT / CTRL_BREAK_EVENT Ctrl+C / Ctrl+Break بلا مهلة
CTRL_CLOSE_EVENT إغلاق الطرفيّة، «إنهاء المهمّة» في مدير المهام (قتل عمليّة قسريّ من علامة تبويب «التفاصيل» خروج فوريّ بلا إشعار، وهو خارج هذا الجدول) نحو 5 ثوانٍ
CTRL_SHUTDOWN_EVENT إغلاق النظام (عمليّات خدمات) نحو 20 ثانية

نقطتان تستحقّان المراقبة. الأولى، أساساً فقط عمليّة تعمل كخدمة تستطيع استقبال CTRL_LOGOFF_EVENT وCTRL_SHUTDOWN_EVENT. تطبيق في جلسة تفاعليّة يُنهى عند الخروج، لذا تصميم ينتظر هذه الإشارات لا يصمد.5 الثانية، عمليّة حمّلت gdi32.dll أو user32.dll تُعامَل تطبيق Windows حتّى إن حسبتها تطبيقاً طرفيّاً، ولا تُستدعى معالجات LOGOFF/SHUTDOWN. الحلّ الرسميّ إنشاء نافذة مخفيّة واستقبال WM_QUERYENDSESSION/WM_ENDSESSION.6

المهلة لكلّ إشارة طرفيّةCtrl+C وCtrl+Break بلا مهلة صريحة؛ إغلاق الطرفيّة نحو 5 ثوانٍ وإشارة إغلاق لعمليّة خدمة نحو 20 ثانية؛ تجاوز ذلك ينهي العمليّة قسراًبلا مهلةنحو 5 ثوانٍنحو 20 ثانيةCTRL_C / BREAKتنظيف HandlerRoutineCTRL_CLOSECTRL_SHUTDOWNقتل قسريّ بعد المهلة

الشكل 6: Ctrl+C وCtrl+Break بلا مهلة صريحة؛ إغلاق الطرفيّة نحو 5 ثوانٍ وإشارة إغلاق لعمليّة خدمة نحو 20 ثانية؛ تجاوز ذلك ينهي العمليّة قسراً.

// Console app: clean up on Ctrl+C and console close
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add);

delegate bool HandlerRoutine(int ctrlType);   // 2 = CTRL_CLOSE_EVENT

static readonly HandlerRoutine s_handler = OnCtrlEvent;  // Keep a reference so GC does not collect it

static bool OnCtrlEvent(int ctrlType)
{
    // Do only cleanup that finishes within 5 seconds
    FlushAndCloseDataFile();
    return false;   // Proceed to the default handler; the process exits
}

static void Main()
{
    SetConsoleCtrlHandler(s_handler, add: true);
    // ...
}

5.2. فخّ .NET ── لا تعتمد على ProcessExit

في .NET طال نمط مخزون «نظِّف فحسب في AppDomain.ProcessExit»، لكنّ من .NET 10 لم يعد وقت التشغيل يوفّر معالجات إشارة إنهاء افتراضيّة، ولا ProcessExit ولا AssemblyLoadContext.Unloading يُطلَقان على CTRL_CLOSE_EVENT/CTRL_SHUTDOWN_EVENT. معالج نظام التشغيل الافتراضيّ ينهي العمليّة فوراً فحسب.7

كيف تغيّر ProcessExit في .NET 10حتّى .NET 9 استقبل معالج الإشارة الافتراضيّ لوقت التشغيل إشارة الإنهاء، ورفع ProcessExit، ثمّ خرج. من .NET 10 لا يوفّر وقت التشغيل معالجاً افتراضيّاً، وتنهي معالجة نظام التشغيل الافتراضيّة العمليّة فوراً، وتسجّل معالجاً بنفسكCTRL_CLOSE / SHUTDOWNحتّى .NET 9: ProcessExitمن .NET 10: خروج فوريّسجّل معالجاً بنفسك

الشكل 7: حتّى .NET 9 استقبل معالج الإشارة الافتراضيّ لوقت التشغيل إشارة الإنهاء، ورفع ProcessExit، ثمّ خرج. من .NET 10 لا يوفّر وقت التشغيل معالجاً افتراضيّاً، وتنهي معالجة نظام التشغيل الافتراضيّة العمليّة فوراً، وتسجّل معالجاً بنفسك.

بدل ذلك، انتقل إلى المسار القانونيّ لكلّ نموذج تطبيق.

  • تطبيق واجهة: FormClosing / SessionEnding من الفصل السابق
  • Generic Host (بما فيه Worker Service): IHostApplicationLifetime وBackgroundService.StopAsync. اجعل مهلة الإيقاف صريحة بـ HostOptions.ShutdownTimeout
  • تطبيق طرفيّ مجرّد: SetConsoleCtrlHandler (أو اشترك في مكافئات SIGINT/SIGTERM بـ PosixSignalRegistration)
أين يستقبل كلّ نموذج تطبيق إشعار الخروجتطبيق واجهة يستخدم FormClosing وSessionEnding زائد خطّاف WM_ENDSESSION للعمل الملتزم؛ Generic Host يستخدم IHostApplicationLifetime وStopAsync؛ تطبيق طرفيّ مجرّد يستخدم SetConsoleCtrlHandler أو PosixSignalRegistration. الاعتماد على ProcessExit لا يُطلَق على مسارات الإشارة الخارجيّةواجهةليس واجهةHostطرفيّةأيّ نموذج تطبيق؟FormClosing / SessionEndingHost أم طرفيّة؟خطّاف ENDSESSIONLifetime + StopAsyncSetConsoleCtrlHandlerاضبط ShutdownTimeoutلا تعتمد على ProcessExit

الشكل 8: تطبيق واجهة يستخدم FormClosing وSessionEnding زائد خطّاف WM_ENDSESSION للعمل الملتزم؛ Generic Host يستخدم IHostApplicationLifetime وStopAsync؛ تطبيق طرفيّ مجرّد يستخدم SetConsoleCtrlHandler أو PosixSignalRegistration. الاعتماد على ProcessExit لا يُطلَق على مسارات الإشارة الخارجيّة.

المهلة تختلف حسب المسار ── نحو 5 ثوانٍ للواجهة وإغلاق الطرفيّة، مهلة SCM في الفصل 6 للخدمة (نحو 20 ثانية، أو القيمة المضبوطة لـ PRESHUTDOWN)، وبلا مهلة صريحة لـ Ctrl+C. على كلّ مسار، غير أنّ المهلة محدودة ولا يمكن الاعتماد عليها، لذا محور التصميم أنّ الحالة العاديّة «محفوظ أصلاً عند كلّ نقطة تحقّق معالجة»، لا «اعمل بجدّ في حدث الخروج».

6. كيف ينبغي أن تتصرّف خدمة Windows ── SHUTDOWN وPRESHUTDOWN

6.1. نوعان من إشعار الإغلاق

الخدمة لا تتأثّر بالخروج، لكنّها تُوقَف عند الإغلاق وإعادة التشغيل. يصل الإشعار كرمز تحكّم من Service Control Manager (SCM)، واستقباله يتطلّب إعلان علم قبول.8

الإعلان الإشعار الذي يصل التوقيت والمهلة
SERVICE_ACCEPT_SHUTDOWN SERVICE_CONTROL_SHUTDOWN تُشعَر أثناء معالجة الإغلاق. الافتراضيّ نحو 20 ثانية، الحدّ الأعلى WaitToKillServiceTimeout
SERVICE_ACCEPT_PRESHUTDOWN SERVICE_CONTROL_PRESHUTDOWN تُشعَر قبل SHUTDOWN. ينتظر SCM حتّى تتوقّف الخدمة أو المهلة
ترتيب إشعارات الإغلاق إلى خدمةعندما يبدأ الإغلاق، تُشعَر أوّلاً الخدمات التي أعلنت PRESHUTDOWN بالمهلة المضبوطة، ثمّ يُرسَل إشعار SHUTDOWN بافتراضيّ نحو 20 ثانية، وتُنهى العمليّة عندما تنقضي المهلةيبدأ الإغلاقPRESHUTDOWN(إن أُعلِن)SHUTDOWN(نحو 20 ثانية)تنقضي المهلة ← خروج

الشكل 9: عندما يبدأ الإغلاق، تُشعَر أوّلاً الخدمات التي أعلنت PRESHUTDOWN بالمهلة المضبوطة، ثمّ يُرسَل إشعار SHUTDOWN بافتراضيّ نحو 20 ثانية، وتُنهى العمليّة عندما تنقضي المهلة.

يمكن ضبط مهلة PRESHUTDOWN بـ ChangeServiceConfig2 ‏(SERVICE_CONFIG_PRESHUTDOWN_INFO)؛ الافتراضيّ 10 ثوانٍ من Windows 10 Creators Update (بناء 15063) فصاعداً، و3 دقائق قبل ذلك.9 إن كنت ما زلت تعمل من المعرفة القديمة أنّ «PRESHUTDOWN يعطيك 3 دقائق»، فعلى نظام تشغيل حاليّ لديك 1/18 فقط من المهلة التي توقّعتها. كذلك، PRESHUTDOWN يحبس إغلاق المنظومة كلّها لتلك المدّة، لذا يقول التوثيق أيضاً إنّه «should be used only in special circumstances».8

ممارسة جانب المعالج مهمّة أيضاً. يجب أن يعود معالج التحكّم خلال 30 ثانية؛ اترك عمل الإيقاف المستغرق للوقت لخيط آخر، أبلغ SERVICE_STOP_PENDING، وعُد فوراً.8

// Win32 service: accept PRESHUTDOWN and leave stop work to a worker
g_status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PRESHUTDOWN;

DWORD WINAPI ServiceHandlerEx(DWORD control, DWORD, LPVOID, LPVOID)
{
    switch (control)
    {
    case SERVICE_CONTROL_PRESHUTDOWN:
    case SERVICE_CONTROL_STOP:
        ReportStatus(SERVICE_STOP_PENDING, /*waitHintMs*/ 3000);
        SetEvent(g_stopEvent);   // Tell the worker to stop and return immediately
        return NO_ERROR;
    }
    return ERROR_CALL_NOT_IMPLEMENTED;
}

// Worker side: if cleanup runs longer than waitHint, keep reporting
// SERVICE_STOP_PENDING periodically while incrementing dwCheckPoint.
// The SCM judges "still alive and making progress" from waitHint and
// checkpoint advance. If reporting stops it can be treated as hung and
// shutdown can proceed. Always report SERVICE_STOPPED when finished

6.2. تصميم لا يعتمد على المهلة

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

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

الشكل 10: إن حفظت عند كلّ نقطة تحقّق معالجة بحيث تكون البيانات غير المحفوظة دائماً في حدّها الأدنى، ينتهي التنظيف عندما يصل إشعار الإيقاف في ثوانٍ. تصميم يحفظ كلّ شيء عند الخروج لن يدخل المهلة، وإنهاء قسريّ يفقد البيانات.

في .NET Worker Service ‏(UseWindowsService)، يُترجَم SERVICE_CONTROL_STOP وSHUTDOWN إلى إيقاف المضيف، ويُستدعى BackgroundService.StopAsync. التنفيذ المخزون وقت الكتابة يقبل عائلة STOP/SHUTDOWN؛ إن احتجت PRESHUTDOWN أيضاً ستحتاج معالجاً موسَّعاً. في الحالين، اجعل HostOptions.ShutdownTimeout صريحاً وأنهِ StopAsync في ثوانٍ. لبناء خدمة عموماً، انظر «كيفيّة إنشاء خدمات Windows وتشغيلها».

7. الاستعادة التلقائيّة بعد إعادة التشغيل

على حاسوب جهاز أو حاسوب بلا مراقبة، نطاق التصميم ليس فقط «النجاة من الإغلاق» بل «العودة وحدك بعد إعادة التشغيل».

7.1. RegisterApplicationRestart وردّ نداء الاستعادة

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

المواصفات التي تستوعبها كما يلي.10

  • يجب أن ينتهي التسجيل قبل حدوث المشكلة (أثناء معالجة WM_QUERYENDSESSION الفرصة الأخيرة في سيناريو تحديث)
  • لمنع حلقة إعادة تشغيل، عمليّة عملت أقلّ من 60 ثانية لا تُعاد تشغيلها
  • عمليّة تعمل بامتياز مرتفع ليست مرشّحة لإعادة تشغيل تلقائيّة (لا يمكن إعادة إنشاء العمليّة بلا موافقة رفع). استعادة تلقائيّة لتطبيق يحتاج رفعاً تُصمَّم بإبقاء الواجهة بامتياز قياسيّ وعزل العمل المميَّز في خدمة، أو بمسار إطلاق صريح كمهمّة جدولة مهامّ «تشغيل بأعلى الامتيازات»
  • إعادة التشغيل بعد انهيار أو تعليق تمرّ بموافقة المستخدم؛ إعادة التشغيل بعد تحديث تلقائيّة
  • للاستعادة عبر إعادة تشغيل نظام تشغيل، يجب أن يستدعي الجانب الذي يطلب إعادة التشغيل (مثبِّت وما شابه) واجهة الإغلاق بأعلام EWX_RESTARTAPPS / SHUTDOWN_RESTARTAPPS

إن سجّلت أيضاً RegisterApplicationRecoveryCallback، يستدعي WER (Windows Error Reporting) ردّ النداء عند انهيار ويعطيك مهلة لحفظ بيانات قيد العمل. إن استغرق الحفظ وقتاً، غير أنّك يجب أن تواصل استدعاء ApplicationRecoveryInProgress ضمن فاصل النبض المحدَّد عند التسجيل وإلا قُطع عمل الاستعادة في المنتصف. عندما ينتهي الحفظ، أبلغ الاكتمال بـ ApplicationRecoveryFinished. «استبدال ملفّ قيد الاستخدام وإعادة التشغيل» وقت تحديث التطبيق إقليم Restart Manager، مشمول بالتفصيل في «كيفيّة استبدال exe أو DLL قيد الاستخدام».

7.2. ARSO ── تسجيل دخول تلقائيّ بعد إعادة تشغيل تحديث

بعد إعادة تشغيل Windows Update، إن لم يسجّل أحد دخولاً، لا تعود تطبيقات جلسة المستخدم. ما يملأ تلك الفجوة هو ARSO (Winlogon Automatic Restart Sign-On). عندما يبدأ Windows Update إعادة تشغيل، يحفظ بأمان بيانات اعتماد آخر مستخدم تفاعليّ، يضبط Autologon، وبعد إعادة التشغيل يسجّل ذلك المستخدم تلقائيّاً ثمّ يقفل الشاشة.11 ثمّة أيضاً أمر مثل shutdown /g يطلب إعادة تشغيل زائد استئناف تطبيقات مسجَّلة. بعض البيئات تعطّل هذا بسياسة تنظيميّة (DisableAutomaticRestartSignOn وما شابه)، لذا عندما تصمِّم استعادة بلا مراقبة، تحقّق من هذا الإعداد كمجموعة. وإن كنت تعتمد على إطلاق تلقائيّ في جلسة مستخدم لعمل خلفيّ تحتاجه دائماً، الحركة الصحيحة جعله خدمة Windows من البداية.

مسار يستعيد به تطبيق نفسه تلقائيّاً بعد إعادة تشغيلإن سجّلت بـ RegisterApplicationRestart قبل حدوث مشكلة، يُعاد تشغيل التطبيق بعد موافقة المستخدم عند انهيار أو عدم استجابة، وبعد تسجيل دخول ARSO التلقائيّ وقفل الشاشة عند إعادة تشغيل مدفوعة بتحديث. عمليّات دون 60 ثانية تشغيل وعمليّات مرتفعة الامتياز خارج النطاقموافقةRegisterApplicationRestartانهيار أو تعليقإعادة تشغيل تحديثإعادة تشغيل التطبيقتسجيل دخول ARSO + قفلليس: دون 60 ثانية / مرتفع

الشكل 11: إن سجّلت بـ RegisterApplicationRestart قبل حدوث مشكلة، يُعاد تشغيل التطبيق بعد موافقة المستخدم عند انهيار أو عدم استجابة، وبعد تسجيل دخول ARSO التلقائيّ وقفل الشاشة عند إعادة تشغيل مدفوعة بتحديث. عمليّات دون 60 ثانية تشغيل وعمليّات مرتفعة الامتياز خارج النطاق.

8. النجاة من فقدان طاقة لا يأتي بإشعار ── تصميم الكتابة وUPS

8.1. كتابة «لا تنكسر مهما قُطعت» ── ملفّ مؤقّت + ReplaceFile

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

النمط القياسيّ الكتابة بالكامل إلى ملفّ مؤقّت على المجلّد نفسه ثمّ التبديل. يغلّف ReplaceFile التسلسل «احفظ في الملفّ الجديد ← نحِّ الأصليّ جانباً ← أعد التسمية ← احذف» في واجهة واحدة، وينقل أيضاً سمات الملفّ الأصليّ كزمن الإنشاء وACL والتدفّقات البديلة (يجب أن تكون الملفّات الثلاثة على المجلّد نفسه).12 File.Replace في .NET يستدعي هذا كما هو.

تدفّق الحفظ والاستعادة بملفّ مؤقّت وReplaceFileعند الحفظ، اكتب بالكامل إلى ملفّ مؤقّت، افرغ، وبدِّل بـ ReplaceFile، تاركاً المحتويات القديمة في .bak. في الإطلاق التالي، تحقّق من الملفّ الأوّليّ وارجع إلى .bak إن كان مكسوراًفي الإطلاق التاليعند الحفظسليممكسورفقدان طاقة في أيّ خطوةتحقّق من الأوّليّاستخدم كما هوارجع إلى .bakأفرغ إلى القرصاكتب ملفّاً مؤقّتاً كاملاًReplaceFile ← .bak

الشكل 12: عند الحفظ، اكتب بالكامل إلى ملفّ مؤقّت، افرغ، وبدِّل بـ ReplaceFile، تاركاً المحتويات القديمة في .bak. في الإطلاق التالي، تحقّق من الملفّ الأوّليّ وارجع إلى .bak إن كان مكسوراً.

// The standard pattern for settings and data: write completely to a temporary file, then swap, and keep the old contents
public static void SaveAtomically(string path, string content)
{
    string dir = Path.GetDirectoryName(path)!;
    string tmp = Path.Combine(dir, Path.GetRandomFileName());  // Create it on the same volume

    try
    {
        using (var fs = new FileStream(tmp, FileMode.CreateNew, FileAccess.Write))
        using (var writer = new StreamWriter(fs))
        {
            writer.Write(content);
            writer.Flush();
            fs.Flush(flushToDisk: true);   // FlushFileBuffers equivalent. Write the OS buffer
                                           // out to disk (device-side cache limits are in 8.2)
        }

        if (File.Exists(path))
            File.Replace(tmp, path, path + ".bak");  // Calls ReplaceFile. Keep the old contents as .bak
        else
            File.Move(tmp, path);
    }
    catch
    {
        // If we fail mid-way, do not leave the temporary file. Repeated failures
        // of a periodic save would fill the volume with complete copies
        try { File.Delete(tmp); } catch { /* Prefer the original exception if delete fails */ }
        throw;
    }
}

بهذا، التشغيل العاديّ يتركك دائماً قادراً على قراءة إمّا «ملفّ قديم كامل» أو «ملفّ جديد كامل». غير أنّ ReplaceFile عمليّة مساحة أسماء متعدّدة الخطوات، والذرّيّة عبر فقدان طاقة غير مضمونة بالمواصفة. لذلك يحتفظ المثال أعلاه بنسخة احتياطيّة (.bak) ── جانب القراءة يتحقّق من الملفّ الأوّليّ عند الإطلاق ويرجع إلى النسخة الاحتياطيّة إن كان مكسوراً، كمجموعة. لا يمكنك استخدام هذا لسجلّات الإلحاق فقط أو CSV، لذا تلك تستخدم صيغة تبني الكسر، مثل «سطر واحد = سجلّ واحد، وتخلَّ عن سطر أخير مكسور عند القراءة».

8.2. نجاح WriteFile ليس بلوغاً إلى القرص

المقدّمة الأخرى أنّ حتّى إن أعاد WriteFile نجاحاً، قد تكون البيانات ما زالت في ذاكرة نظام التشغيل المؤقّتة فحسب. يضع Windows قراءات الملفّات وكتاباتها على مخزن النظام ويعكسها إلى القرص دوريّاً بكتابة كسولة. لإيصال بيانات إلى القرص يقيناً، إمّا أفرغ صراحةً بـ FlushFileBuffers، أو حدِّد FILE_FLAG_WRITE_THROUGH عند CreateFile بحيث تمرّ كلّ كتابة عبر الذاكرة المؤقّتة. بيانات تعريف نظام الملفّات تُخزَّن مؤقّتاً دائماً، لذا تأكيد البيانات الوصفيّة يحتاج أيضاً إفراغاً أو write-through.13

استدعاء FlushFileBuffers في كلّ مرّة غير كفء، غير أنّ التوثيق أيضاً يشجّع النظر في FILE_FLAG_NO_BUFFERING+WRITE_THROUGH بدل الاستدعاءات المتكرّرة.13 عمليّاً، «أفرغ فقط عند نقطة تحقّق معاملة أو قبيل إغلاق الملفّ» تسوية واقعيّة. آليّة هذه الطبقة ── مدير الذاكرة المؤقّتة، والكتابة الكسولة، وحقيقة أنّ «أفرغت وما زال قد لا يكون بلغ القرص» بسبب ذاكرة عتاد مؤقّتة ── مشمولة بعمق في «مدير الذاكرة المؤقّتة: متى يصل WriteFile فعليّاً إلى القرص؟».

8.3. UPS ومراقبة البطاريّة ── تحويل فقدان طاقة إلى إغلاق

الإجراء المضادّ الحقيقيّ لفقدان الطاقة على حاسوب جهاز هو UPS. فكِّر في دور UPS ليس «إيقاف انقطاع» بل تحويل «فقدان طاقة بلا إشعار» إلى «إغلاق مخطَّط بإشعار». التصميم إعداد على مرحلتين.

  1. تصميم المهلة: زمن إمساك بطاريّة UPS > مجموع «اكشف التحويل إلى البطاريّة ← تنظيف التطبيق والخدمة ← اكتمال إغلاق نظام التشغيل». إن كانت معالجة إيقاف الخدمة بطيئة، لم تعد هذه المعادلة تصمد (القسم 6.2)
  2. الكشف: التحويل من تيّار متردّد إلى بطاريّة، وانخفاض السعة المتبقّية، يُشعَران بحدث PBT_APMPOWERSTATUSCHANGE. تطبيق له نافذة يستقبله كـ WM_POWERBROADCAST؛ خدمة بلا نافذة تعلن SERVICE_ACCEPT_POWEREVENT وتستقبله كـ SERVICE_CONTROL_POWEREVENT في HandlerEx (WM_POWERBROADCAST لا يصل إلى معالج تحكّم خدمة). عند الاستقبال، استدعِ GetSystemPowerStatus، تحقّق من ACLineStatus (ما إذا على تيّار متردّد) وBatteryLifePercent، وانتقل إلى مقاطعة القياس، والحفظ، وطلب الإغلاق15
تدفّق تحويل فقدان طاقة إلى إغلاق مخطَّط بـ UPSعندما يحوِّل انقطاع UPS إلى البطاريّة، يُشعَر PBT_APMPOWERSTATUSCHANGE، يُتحقَّق من حالة الطاقة، والحفظ زائد طلب إغلاق يحوّلان فقدان طاقة بلا إشعار إلى إغلاق مخطَّط بإشعارانقطاعUPS إلى البطاريّةPBT_APMPOWERSTATUSCHANGEGetSystemPowerStatusقاطع واحفظاطلب الإغلاقتدفّق الإشعار المعتاد(3–6)

الشكل 13: عندما يحوِّل انقطاع UPS إلى البطاريّة، يُشعَر PBT_APMPOWERSTATUSCHANGE، يُتحقَّق من حالة الطاقة، والحفظ زائد طلب إغلاق يحوّلان فقدان طاقة بلا إشعار إلى إغلاق مخطَّط بإشعار.

UPS نموذجيّ متّصل بـ USB يظهر لـ Windows بطاريّة، لذا يمكنك كشفه بهذه الواجهة القياسيّة. إن كان لبرمجيّات إدارة البائع ميزة «أغلق نظام التشغيل عند N% متبقّية»، تحقّق أيضاً من أنّ العتبة تصطفّ مع زمن تنظيف تطبيقك. الاستئناف من السكون أو الإسبات، ومشكلات التشغيل الطويل، محور منفصل، مشمول في «السكون والإسبات وModern Standby والتطبيقات طويلة التشغيل».

9. كيف تتحقّق ── تجربة الإغلاق بأمان

معالجة الإغلاق تميل إلى أن تصبح «كتبناها لكن لم نجرّبها في ظروف مكافئة للإنتاج». احتفظ بإجراء للتحقّق بأمان.

  • جرِّبها على جهاز اختبار أو آلة افتراضيّة: لا تجرّبها أوّلاً على حاسوب جهاز إنتاج. في بيئة اختبار بنقطة تحقّق Hyper-V (لقطة)، كرِّر الإغلاق وإعادة التشغيل وفقدان طاقة قسريّ (إطفاء الآلة الافتراضيّة). غير أنّ «إطفاء» آلة افتراضيّة يعيد إنتاج «توقّف نظام التشغيل الضيف بلا إشعار» فقط؛ لا يعيد إنتاج اختفاء ذاكرة قرص فيزيائيّ متطايرة أو كسراً يعتمد على المتحكّم. إن شحنته حاسوب جهاز، التحقّق النهائيّ اختبار قطع طاقة حقيقيّ على عتاد مكافئ للإنتاج
  • تحقّق سريع بالخروج: مسار WM_QUERYENDSESSION ← WM_ENDSESSION يعمل أيضاً عند الخروج (الفرق الوحيد أنّ بتّة ENDSESSION_LOGOFF مضبوطة في lParam)، لذا يمكنك تأكيد سلوك شيفرة التنظيف بسهولة على جهاز تطوير1
  • جرِّب إغلاقاً كاملاً وهجيناً منفصلين: جرِّب shutdown /s /t 0 (كامل)، وshutdown /s /hybrid /t 0 (السلوك الافتراضيّ)، وshutdown /r /t 0 (إعادة تشغيل) كلّاً2
  • قِس كم يستغرق التنظيف: اكتب ختماً زمنيّاً إلى السجلّ عند بداية دالّة التنظيف ونهايتها، وقِس ما إذا دخلت في 5 ثوانٍ (أو المهلة المضبوطة لخدمة)
عمليّات للتحقّق وما يمكن لكلّ منها تأكيدهالخروج تحقّق مريح من مسار الإشعار؛ أوامر الإغلاق الكامل والهجين وإعادة التشغيل تؤكّد مسار الإنتاج والمهلة؛ إطفاء آلة افتراضيّة يختبر الصمود أمام توقّف مفاجئ؛ اختبار قطع طاقة فيزيائيّ التحقّق النهائيّ بما فيه التخزين الفيزيائيّخروجمسار QUERY ← ENDSESSIONshutdown /s /hybrid /rمسار الإنتاج + المهلةإطفاء آلة افتراضيّةتوقّف ضيف مفاجئقطع طاقة فيزيائيّبما فيه التخزين(نهائيّ)

الشكل 14: الخروج تحقّق مريح من مسار الإشعار؛ أوامر الإغلاق الكامل والهجين وإعادة التشغيل تؤكّد مسار الإنتاج والمهلة؛ إطفاء آلة افتراضيّة يختبر الصمود أمام توقّف مفاجئ؛ اختبار قطع طاقة فيزيائيّ التحقّق النهائيّ بما فيه التخزين الفيزيائيّ.

للعزل بعد الواقعة، سجلّ الأحداث (النظام) مفيد. عند إغلاق أو إعادة تشغيل عاديّين، يُسجَّل معرِّف الحدث 1074 (أيّ عمليّة بدأت الإغلاق، لمن، ولأيّ سبب). عند فقدان طاقة مفاجئ أو انهيار لا يوجد 1074، وفي الإقلاع التالي يُسجَّل معرِّف الحدث 41 (Kernel-Power) و6008 (The previous system shutdown was unexpected).14 «ماذا حدث ليلاً» يبدأ هنا.

# Check the recent history of shutdown-related events
Get-WinEvent -FilterHashtable @{ LogName = 'System'; Id = 1074, 6008, 41 } -MaxEvents 20 |
    Select-Object TimeCreated, Id, ProviderName, Message |
    Format-List

إن أظهر 1074 «إعادة تشغيل من Windows Update» وكانت بيانات التطبيق مكسورة، فالمشكلة شيفرة التنظيف. 6008/41 يظهران فقط «إغلاقاً غير متوقَّع»؛ يُسجَّلان أيضاً لشاشة زرقاء (انهيار) أو إعادة ضبط قسريّة، لا فقدان طاقة فحسب. إن كان BugcheckCode لـ 41 غير صفر فهو انهيار؛ إن كان 0 ولا يوجد تفريغ ذاكرة أيضاً، ففقدان طاقة مرجَّح ── اعزل السبب من المعلومات المحيطة، ومتى علمت أنّه فقدان طاقة، تصميم الكتابة في الفصل 8 وUPS الخطوة التالية.

10. الخلاصة

  • الإغلاق «حدث عاديّ سيأتي، عاجلاً أم آجلاً». المهلة بعد الإشعار، من حيث المبدأ، نحو 5 ثوانٍ فقط، لذا الشرط المسبق حفظ تلقائيّ متكرّر بحيث يُقلَّل «ما تفعله عند الخروج».
  • على أنظمة تشغيل العميل من Windows 8 فصاعداً، إن كان بدء التشغيل السريع مفعّلاً، «إيقاف التشغيل» إغلاق هجين والنواة تُسبِت فحسب. التصفير الكامل الوحيد «إعادة التشغيل» ── اكتب «إعادة التشغيل» في إجراء الحادث.
  • تطبيق واجهة يعيد TRUE فوراً إلى WM_QUERYENDSESSION، وينفّذ تنظيفاً ملتزَماً في WM_ENDSESSION. FormClosing وSessionEnding في WinForms/WPF يقابلان مرحلة الاستعلام، لذا ما تفعله هناك حفظ لقطة متكافئ على الأكثر. لا تعرض حواراً أثناء الإغلاق.
  • عمليّة لا يمكن مقاطعتها حقّاً تُحمى بعرض سبب بـ ShutdownBlockReasonCreate. غير أنّه لا ضمان في أيّ مكان أنّك تستطيع الحظر.
  • تطبيق طرفيّ يستقبل الإشعار بـ SetConsoleCtrlHandler؛ خدمة بـ SERVICE_ACCEPT_PRESHUTDOWN/SHUTDOWN. مهلة PRESHUTDOWN الافتراضيّة 10 ثوانٍ على نظام تشغيل حاليّ. في .NET، توقّف عن الاعتماد على ProcessExit وانتقل إلى المسار القانونيّ لنموذج التطبيق.
  • الاستعادة بعد إعادة التشغيل يمكن أن تكون بلا مراقبة بـ RegisterApplicationRestart (زائد ردّ نداء استعادة) وARSO.
  • فقدان الطاقة لا يأتي بإشعار. استعدّ بتبديل ملفّ مؤقّت + ReplaceFile (كمجموعة مع نسخة احتياطيّة + تحقّق وقت التحميل)، وإفراغ عند نقاط التحقّق، وUPS «يحوِّل فقدان طاقة إلى إغلاق مخطَّط».
الصورة الكلّيّة لمعالجة الإغلاقلنهاية بإشعار، استجب بتنظيف يستطيع إغلاق المحلّ في ثوانٍ وانتقل إلى استعادة تلقائيّة بعد إعادة التشغيل؛ لفقدان طاقة بلا إشعار، استعدّ بكتابة لا تنكسر مهما قُطعت وUPS، وتحقّق بما فيه على عتاد فيزيائيّ. هذان العمودان خلاصة المقالبإشعاربلا إشعاركيف ينتهيتنظيف ثوانٍ(3–6)كتابة آمنة + UPS(8)استعادة تلقائيّة بعد إعادة التشغيل(7)تحقّق على العتاد(9)

الشكل 15: لنهاية بإشعار، استجب بتنظيف يستطيع إغلاق المحلّ في ثوانٍ وانتقل إلى استعادة تلقائيّة بعد إعادة التشغيل؛ لفقدان طاقة بلا إشعار، استعدّ بكتابة لا تنكسر مهما قُطعت وUPS، وتحقّق بما فيه على عتاد فيزيائيّ. هذان العمودان خلاصة المقال.

  • تحقّق بأمان على آلة افتراضيّة وبالخروج، واعزل بعد الواقعة بمعرِّفات الأحداث 1074/41/6008.

في المرّة التالية التي تضيف فيها ميزة إلى التطبيق، اسأل نفسك مرّة: إن وصل WM_ENDSESSION في منتصف هذا العمل، أو سُحبت الطاقة، ماذا يبقى في الإطلاق التالي؟ كتابة ذلك الجواب في التصميم أقصر طريق إلى ألا تقضي صباحاً واقفاً أمام حاسوب جهاز ويداك على رأسك.

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

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

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

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

  1. Microsoft Learn, WM_QUERYENDSESSION message. حول إرسال WM_QUERYENDSESSION عند نهاية الجلسة ووجوب إعادة التطبيق TRUE واحترام قصد المستخدم (افتراضيّ DefWindowProc TRUE أيضاً)؛ وحول تأجيل التنظيف حتّى WM_ENDSESSION؛ وحول عرض النظام بعد 5 ثوانٍ واجهة للتطبيقات التي تمنع الإغلاق وقدرة المستخدم على الإنهاء القسريّ؛ ومعنى بتّات ENDSESSION_LOGOFF/CLOSEAPP/CRITICAL في lParam؛ وحول عدم إمكان التمييز بين الإغلاق وإعادة التشغيل؛ وحول وجوب حفظ البيانات كثيراً بحيث يقلّ ما يجب حفظه عند الخروج.  2 3 4 5 6 7

  2. Microsoft Learn, Fast startup causes hibernation or shutdown to fail in Windows 10 or Windows 8.1. حول عدم إغلاق جلسة النواة مع بدء التشغيل السريع ومعاملتها إسباتاً، وحفظ حالة النواة وتعريفات الأجهزة في hiberfil.sys؛ وحول تنفيذ «إعادة التشغيل» دائماً إقلاعاً كاملاً لأنّ حالة Windows جديدة بالكامل مطلوبة؛ وحول تفعيل بدء التشغيل السريع افتراضيّاً وعدم التوصية بتعطيله؛ وحول كون افتراضيّ Shutdown.exe إغلاقاً كاملاً، مع خيار /hybrid منتجاً سلوكاً هجيناً.  2 3 4 5

  3. Microsoft Learn, ShutdownBlockReasonCreate function (winuser.h). حول استدعائها عند بدء عمليّة لا يمكن مقاطعتها لتسجيل سلسلة سبب واستدعاء ShutdownBlockReasonDestroy عند الانتهاء؛ وحول إمكان استدعائها فقط من الخيط الذي أنشأ النافذة؛ وحول أنّ المستخدم لن يقرأ السبب إلا ثواني، لذا ينبغي أن تكون السلسلة قصيرة وواضحة.  2 3

  4. Microsoft Learn, Shutdown Changes for Windows Vista. حول إمكان تأخير الاستجابة لـ WM_QUERYENDSESSION/WM_ENDSESSION بـ 5 ثوانٍ لكلّ منهما ثمّ اختيار المستخدم المتابعة أو الإلغاء؛ وحول عجز تطبيق طرفيّ أو تطبيق بلا نافذة مرئيّة عن إجهاض الإغلاق وإنهائه تلقائيّاً بعد 5 ثوانٍ بلا ردّ أو استجابة FALSE؛ وحول وجوب تسجيل سبب بـ ShutdownBlockReasonCreate إن احتيج حظر؛ وحول وجوب ألا يعتمد تطبيق على القدرة على حظر الإغلاق.  2 3 4

  5. Microsoft Learn, HandlerRoutine callback function. أحداث CTRL_C/BREAK/CLOSE/LOGOFF/SHUTDOWN التي يستقبلها معالج مسجَّل بـ SetConsoleCtrlHandler؛ وحول كون المهلة الافتراضيّة لـ CTRL_CLOSE_EVENT نحو 5000 مليّ ثانية ولـ CTRL_SHUTDOWN_EVENT على عمليّة خدمة نحو 20000 مليّ ثانية؛ وحول استقبال CTRL_LOGOFF/SHUTDOWN_EVENT أساساً من الخدمات فقط لأنّ تطبيقاً تفاعليّاً يُنهى عند الخروج؛ وحول تشغيل المعالج على خيط منفصل.  2 3

  6. Microsoft Learn, SetConsoleCtrlHandler function. حول معاملة عمليّة حمّلت gdi32.dll أو user32.dll تطبيق Windows وعدم استدعاء معالجات CTRL_LOGOFF_EVENT/CTRL_SHUTDOWN_EVENT؛ وحول كون الحلّ إنشاء نافذة مخفيّة ومعالجة WM_QUERYENDSESSION/WM_ENDSESSION؛ وحول إمكان ألا تعمل دوالّ الطرفيّة بشكل صحيح أثناء معالجة الإشارة.  2

  7. Microsoft Learn, .NET runtime no longer provides default termination signal handlers. حول توقّف وقت التشغيل من .NET 10 عن توفير معالج افتراضيّ لـ CTRL_SHUTDOWN_EVENT/CTRL_CLOSE_EVENT في Windows (مكافئات SIGTERM/SIGHUP في يونكس)؛ وحول إنهاء معالجة نظام التشغيل الافتراضيّة التطبيق فوراً وتوقّف إطلاق AppDomain.ProcessExit وAssemblyLoadContext.Unloading؛ وحول وجوب تسجيل معالجة إشارة مناسبة لنموذج التطبيق في مكتبة أعلى أو في شيفرة التطبيق.  2

  8. Microsoft Learn, Service Control Handler Function. حول استقبال خدمة أعلنت SERVICE_ACCEPT_PRESHUTDOWN أوّلاً SERVICE_CONTROL_PRESHUTDOWN، ثمّ استقبال خدمة SERVICE_ACCEPT_SHUTDOWN ‏SERVICE_CONTROL_SHUTDOWN؛ وحول كون المهلة الافتراضيّة عند الإغلاق نحو 20 ثانية والسقف عند إعادة تشغيل نظام التشغيل WaitToKillServiceTimeout؛ وحول وجوب عدم تمديد هذه القيمة؛ وحول وجوب عودة معالج التحكّم خلال 30 ثانية، والإبلاغ عن STOP_PENDING وتلميح انتظار، وترك العمل الطويل لخيط آخر؛ وحول وجوب إنهاء التنظيف بأسرع ما يمكن مع تشغيل UPS في الاعتبار؛ وحول عدم اعتبار SCM عند الإغلاق، افتراضيّاً، التبعيّات.  2 3 4 5

  9. Microsoft Learn, SERVICE_PRESHUTDOWN_INFO structure (winsvc.h). حول انتظار SCM بعد إشعار PRESHUTDOWN حتّى تتوقّف الخدمة أو المهلة؛ وحول كون المهلة الافتراضيّة 10 ثوانٍ من Windows 10 Creators Update (بناء 15063) فصاعداً و3 دقائق قبل ذلك؛ وحول ضبطها بـ ChangeServiceConfig2؛ وحول إمكان مواصلة تحديث الحالة أثناء SERVICE_STOP_PENDING.  2

  10. Microsoft Learn, RegisterApplicationRestart function (winbase.h). حول إمكان تسجيل إعادة تشغيل لانهيار وعدم استجابة وتحديث وإعادة تشغيل حاسوب مصاحبة لتحديث؛ وحول إمكان تحديد وسائط سطر أوامر لإعادة التشغيل؛ وحول وجوب التسجيل قبل حدوث المشكلة وكون معالجة WM_QUERYENDSESSION الفرصة الأخيرة في سيناريو تحديث؛ وحول عدم إعادة تشغيل عمليّة دون 60 ثانية تشغيل؛ وحول مرور إعادة التشغيل بعد انهيار أو تعليق بموافقة المستخدم؛ وحول احتياج عبور إعادة تشغيل نظام تشغيل إغلاقاً بـ EWX_RESTARTAPPS/SHUTDOWN_RESTARTAPPS.  2 3

  11. Microsoft Learn, Winlogon automatic restart sign-on (ARSO). حول حفظ Windows Update بيانات اعتماد آخر مستخدم تفاعليّ وضبط Autologon عندما يبدأ إعادة تشغيل تلقائيّة؛ وحول تسجيل دخول المستخدم تلقائيّاً بعد إعادة التشغيل وقفل الجلسة؛ وحول حذف بيانات الاعتماد المحفوظة بعد تسجيل دخول ناجح؛ وحول إمكان ضبطه بسياسة مجموعة (DisableAutomaticRestartSignOn وما شابه).  2

  12. Microsoft Learn, ReplaceFileW function (winbase.h). حول تغليف ReplaceFile في دالّة واحدة الخطوات المتعدّدة المكافئة لـ «احفظ في الملفّ الجديد، أعد تسمية الأصليّ مؤقّتاً، أعد تسمية الملفّ الجديد، احذف الأصليّ»؛ وحول حفاظه على سمات الملفّ الأصليّ كزمن الإنشاء وDACL والتشفير والضغط والتدفّقات المسماة؛ وحول وجوب كون النسخة الاحتياطيّة والملفّ المستبدَل وملفّ الاستبدال على المجلّد نفسه.  2

  13. Microsoft Learn, File Caching. حول دخول الكتابات ذاكرة النظام المؤقّتة افتراضيّاً وانعكاسها إلى القرص بكتابة كسولة؛ وحول كتابة FILE_FLAG_WRITE_THROUGH فوراً إلى القرص؛ وحول إمكان إفراغ FlushFileBuffers صراحةً؛ وحول تخزين بيانات تعريف نظام الملفّات مؤقّتاً دائماً، لذا تأكيد البيانات الوصفيّة يتطلّب إفراغاً أو write-through.  2 3

  14. Microsoft Learn, Troubleshoot unexpected reboots using system event logs. حول تسجيل إعادة تشغيل عاديّة معرِّف الحدث 1074 (أيّ عمليّة بدأت الإغلاق، لمن، ولأيّ سبب)؛ وحول تسجيل إعادة تشغيل غير متوقَّعة معرِّف الحدث 41 (Kernel-Power) و6008 (كان الإغلاق السابق غير متوقَّع)؛ وحول قدرة هذه المعرِّفات على عزل نوع إعادة التشغيل.  2

  15. Microsoft Learn, PBT_APMPOWERSTATUSCHANGE event. حول إشعار هذا الحدث عبر WM_POWERBROADCAST عند تحويل بين بطاريّة وتيّار متردّد أو انخفاض السعة المتبقّية؛ وحول وجوب استدعاء GetSystemPowerStatus عند الاستقبال والتحقّق من حقول SYSTEM_POWER_STATUS مثل ACLineStatus وBatteryFlag وBatteryLifePercent. 

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

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

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

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

مشكلة لم تختفِ بعد «إيقاف التشغيل» اختفت بعد «إعادة التشغيل». لماذا؟
على أنظمة تشغيل العميل من Windows 8 فصاعداً، عندما يكون بدء التشغيل السريع مفعّلاً (الافتراضيّ على معظم الحواسيب التي تدعم الإسبات)، يستخدم «إيقاف التشغيل» آليّة تُدعى الإغلاق الهجين. يُخرَج المستخدم، لكنّ حالة النواة والتعريفات تُحفَظ في ملفّ الإسبات وتُستعاد كما هي في الإقلاع التالي. بعبارة أخرى، نواة نظام التشغيل لم تُصفَّر. أمّا «إعادة التشغيل» فتنفّذ دائماً إقلاعاً كاملاً، لذا تُصفَّر مشكلات التعريفات والخدمات. اكتب «إعادة التشغيل» في إجراء العزل، لا «أوقف التشغيل وأعد الطاقة». إن أردت إغلاقاً كاملاً من سطر الأوامر، يمكنك استخدام shutdown /s.
هل يمكنني إيقاف الإغلاق حتّى ينتهي التطبيق من الحفظ؟
يمكنك أن تطلب منه الانتظار مؤقّتاً، لكن لا يمكنك إيقافه بموثوقيّة. إن سجّلت سلسلة سبب بـ ShutdownBlockReasonCreate فقط أثناء سير عمليّة لا يمكن مقاطعتها، يظهر ذلك السبب على شاشة «هذا التطبيق يمنع الإغلاق» ويستطيع المستخدم أن يقرّر المتابعة أو الإلغاء. غير أنّ المستخدم ما زال يستطيع اختيار المتابعة القسريّة، وقد لا ينتظر إغلاق قسريّ أو إعادة تشغيل مدفوعة بتحديث أصلاً. النهج الصحيح إذن ليس «الحظر»، بل حفظاً تلقائيّاً متكرّراً بحيث يقلّ ما هو معرَّض للخطر، زائد تنظيفاً مصمَّماً لينتهي في ثوانٍ من إشعار الخروج.
إيقاف خدمة Windows يستغرق وقتاً طويلاً. هل يمكنني تمديد مهلة الإغلاق؟
في الإعداد الافتراضيّ الذي يستقبل SERVICE_CONTROL_SHUTDOWN، المهلة نحو 20 ثانية وتعتمد على قيمة سجلّ WaitToKillServiceTimeout. إعادة كتابة تلك القيمة من جانب التطبيق لتمديدها غير موصى بها. إن احتجت مهلة أطول، يمكنك إعلان SERVICE_ACCEPT_PRESHUTDOWN واستقبال SERVICE_CONTROL_PRESHUTDOWN؛ تُشعَر أبكر من غيرك، ويمكن ضبط المهلة بـ ChangeServiceConfig2 (الافتراضيّ 10 ثوانٍ من Windows 10 Creators Update فصاعداً، و3 دقائق قبل ذلك). غير أنّ PRESHUTDOWN يوقف الإغلاق كلّه لتلك المدّة، لذا قصره على حالات تحتاجها حقّاً، وصمِّم أساساً عمل الإيقاف نفسه لينتهي في ثوانٍ.
هل من الآمن تنفيذ تنظيف الإغلاق في AppDomain.ProcessExit في .NET؟
أوصي بعدم الاعتماد عليه. تاريخيّاً سجّل وقت التشغيل معالج إشارة افتراضيّاً، وكان ProcessExit يُطلَق على CTRL_CLOSE_EVENT وCTRL_SHUTDOWN_EVENT، لكن من .NET 10 لم يعد وقت التشغيل يوفّر معالجات إشارة إنهاء افتراضيّة، ولم يعد ProcessExit يُطلَق في تلك الحالات. نفِّذ التنظيف على مسار الإشعار الذي يطابق نموذج التطبيق: تطبيقات الواجهة تستخدم FormClosing أو SessionEnding (تلك إشعارات مرحلة الاستعلام، لذا قصُرها على حفظات متكافئة؛ التنظيف الذي يمكن تشغيله فقط بعد التزام الجلسة ينتمي إلى خطّاف WM_ENDSESSION)؛ Generic Host / Worker Service يستخدمان IHostApplicationLifetime وStopAsync؛ التطبيقات الطرفيّة تستخدم SetConsoleCtrlHandler أو PosixSignalRegistration.
كيف أمنع فساد الملفّات بفقدان طاقة مفاجئ؟
فقدان الطاقة لا يأتي بأيّ إشعار أصلاً، لذا الخيار الوحيد هو الكتابة بطريقة لا تنكسر مهما قُطعت الطاقة. الأساس ألا تكتب فوق الملفّ الأصليّ في مكانه: اكتب بالكامل إلى ملفّ مؤقّت على المجلّد نفسه، افرغ، وبدِّل بـ ReplaceFile (File.Replace في .NET). في التشغيل العاديّ يتركك هذا قادراً على قراءة ملفّ قديم كامل أو ملفّ جديد كامل، لكنّ ذرّيّة ReplaceFile عبر فقدان طاقة غير مضمونة بالمواصفة، لذا احتفظ بنسخة احتياطيّة (الوسيط الثالث) ونفِّذ استعادة وقت التحميل تتحقّق من الملفّ الأوّليّ وترجع إلى النسخة الاحتياطيّة إن كان مكسوراً. كذلك، نجاح WriteFile لا يعني أنّ البيانات بلغت القرص، لذا عند نقاط تحقّق مهمّة أكِّد الكتابة بـ FlushFileBuffers أو FILE_FLAG_WRITE_THROUGH. على حواسيب الأجهزة الإعداد القياسيّ الجمع بين هذا وUPS، وكشف التحويل إلى البطاريّة، والانتقال إلى إغلاق آمن.

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

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

غو كومورا

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

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

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