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

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

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

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

غو كومورا (2026). إغلاق Windows كما يراه تطبيقك ── النجاة من إشعارات الخروج وإعادة التشغيل وفقدان الطاقة بشكل صحيح. شركة كومورا سوفت ذ.م.م.. https://comcomponent.com/ar/blog/windows-shutdown-handling-for-apps/

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

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

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

لموظّفي تقنيّة المعلومات في المنشآت الصغيرة والمتوسّطة ومطوّري تطبيقات Windows، خصوصاً من يعملون مع حواسيب الأجهزة والتطبيقات طويلة التشغيل، يسير هذا المقال عبر اختيار مسار إشعار، وتنفيذه، والاستعادة بعد إعادة التشغيل، والاستعداد لفقدان الطاقة، والتحقّق من النتيجة. الأساس التقنيّ مصادر Microsoft Learn الأوّليّة، حتّى أغسطس 2026، التي استند إليها المقال الأصليّ.

1. الخلاصة أوّلاً ── قلِّل ما يجب حفظه قبل انتظار إشعار

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

بدل حفظ كمّ كبير من البيانات بعد إشعار الخروج، احفظ عند كلّ معلم من العمل وأبقِ الفرق المتبقّي عند الخروج صغيراً. لتطبيقات الواجهة وإغلاق الطرفيّة، نحو 5 ثوانٍ الرقم المحوريّ. للخدمات مهلة مختلفة، لكن لا شيء من هذه زمن مضمون أن تحصل عليه كاملاً.123

1.1. اختر مسار الإشعار لتطبيقك

نوع التطبيق أين يصل طلب الخروج أوّل ما تستوعبه القسم الذي تقرأه
تطبيق واجهة Win32 WM_QUERYENDSESSION وWM_ENDSESSION أجب على الاستعلام بـ TRUE فوراً، كقاعدة. نفِّذ التنظيف بعد التزام الخروج في WM_ENDSESSION القسم 3
WinForms / WPF FormClosing / SessionEnding، زائد خطّاف رسالة عند الحاجة كلا الحدثين ينتميان إلى مرحلة الاستعلام. انقل التنظيف الذي لا يمكن التراجع عنه إلى الإشعار الملتزَم القسم 3
تطبيق طرفيّ بسيط SetConsoleCtrlHandler وما شابه الإغلاق والخروج/الإغلاق يُشعَران بشروط مختلفة القسم 5
خدمة Windows SHUTDOWN / PRESHUTDOWN من SCM أعلن علم التحكّمات المقبولة وعُد من معالج التحكّم فوراً القسم 6
Generic Host / Worker Service IHostApplicationLifetime وStopAsync ركِّز التنظيف على مسار إيقاف الـ Host واضبط ShutdownTimeout صراحة القسمان 5 و6

في .NET، لا تعتمد على AppDomain.ProcessExit وحده. من .NET 10 فصاعداً، لا يوفّر وقت التشغيل معالجاً افتراضيّاً لـ CTRL_CLOSE_EVENT / CTRL_SHUTDOWN_EVENT. مسار الإشارة الخارجيّة هذا منفصل عن خروج عاديّ كالعودة من Main. يغطّي القسم 5.2 التفاصيل.4

1.2. استعدادان لازمان خارج معالجة الإشعار

عندما يصل الإشعار، لا تسأل المستخدم «هل تريد الحفظ؟»؛ أنهِ بتنظيف قصير واخرج. الحظر المؤقّت لعمل لا يمكن مقاطعته حقّاً مشروح في القسم 4، والاستعادة التلقائيّة بعد إعادة التشغيل في القسم 7.

الاستعداد الآخر هو ترك بيانات ما زالت قابلة للقراءة بعد فقدان طاقة لا يأتي بإشعار. الحفظ إلى ملفّ مؤقّت، والإفراغ، والتبديل، والاحتفاظ بنسخة احتياطيّة، والتحقّق عند الإقلاع مشروحة معاً في القسم 8. متى نُفِّذ مسار الإشعار، تابع لفحص تصميم الحفظ والاستعادة في القسم 8 والتحقّق في القسم 9.

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

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

في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 14، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle

2. ميّز أوّلاً ── الخروج، والإغلاق، وإعادة التشغيل، وفقدان الطاقة

2.1. انظر إلى جلسة المستخدم والنواة منفصلين

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

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

في WM_QUERYENDSESSION للواجهة، تشير بتّة ENDSESSION_LOGOFF في lParam إلى خروج. إن كان lParam 0 فهو إغلاق أو إعادة تشغيل، ولا يمكن تمييز الاثنين. عامل lParam قناع بتّات.1

لبيانات التطبيق غير المحفوظة، يستدعي الخروج والإغلاق الاستعداد نفسه. لا تقسمهما إلى «لا حاجة للحفظ عند الخروج»؛ وجِّه كليهما إلى روتين حفظ مشترك. غير أنّ ذلك لا يجعل شروط الإشعار للتطبيقات الطرفيّة والخدمات متطابقة؛ افحص المسارات في القسمين 5 و6 لكلّ منهما.

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

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

2.2. لماذا «الإغلاق لا يصلحه، لكنّ إعادة التشغيل تفعل»

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

هذا السلوك مشروط. حيث يُعطَّل الإسبات (powercfg /hibernate off)، وحيث يُوقَف بدء التشغيل السريع بسياسة أو في خيارات الطاقة، وعلى Windows Server، تحصل على الإغلاق الكامل التقليديّ. افحص إعدادات خيارات الطاقة، واستخدم powercfg /a لفحص ما إذا كان بدء التشغيل السريع متاحاً.

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

بدء التشغيل السريع مقابل إقلاع كاملإغلاق مع بدء تشغيل سريع مفعّل يحفظ حالة النواة والتعريفات ويستعيدها، بينما إغلاق كامل أو إعادة تشغيل يهيّئ كلّ شيء بإقلاع كاملنعملاإيقاف التشغيلاستخدم بدء التشغيل السريع؟أسبِت النواة والتعريفاتاستعد الحالة عند الإقلاع التاليإغلاق كاملهيّئ بإقلاع كامل في المرّة التاليةإعادة التشغيل

الشكل 3: قطع الطاقة ليس نفسه تصفير النواة والتعريفات.

من سطر الأوامر، يطلب shutdown /s إغلاقاً كاملاً صريحاً وshutdown /s /hybrid السلوك الهجين. Shutdown.exe يفترض إغلاقاً كاملاً، لذا مهمّ ألا تفترض أنّه نفسه عنصر «إيقاف التشغيل» على الشاشة.5

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

3. تطبيقات الواجهة ── افصل الاستعلام عن الخروج الملتزَم

3.1. WM_QUERYENDSESSION هو السؤال، WM_ENDSESSION هو النتيجة

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

الرسالة المعنى ما تفعله
WM_QUERYENDSESSION الاستعلام «هل الخروج مقبول؟» أعد TRUE فوراً، كقاعدة. DefWindowProc أيضاً يفترض TRUE
WM_ENDSESSION، wParam=TRUE نهاية الجلسة ملتزَمة نفِّذ التنظيف القصير: احفظ، افصل، وما شابه
WM_ENDSESSION، wParam=FALSE أُلغيَت نهاية الجلسة التطبيق يظلّ يعمل. لا تنفِّذ تنظيفاً ممكناً فقط بعد التزام الخروج

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

استعلام الواجهة ونتيجة الخروجحتّى بعد إعادة TRUE إلى WM_QUERYENDSESSION يمكن لتطبيق آخر إلغاء الخروج، لذا يعمل التنظيف بعد الالتزام فقط عندما يكون wParam لـ WM_ENDSESSION TRUEنعملاWM_QUERYENDSESSIONأعد TRUE فوراً، كقاعدةWM_ENDSESSIONهل wParam TRUE؟تنظيف بعد الالتزامأُلغيَ الخروج؛ ظلّ يعمل

الشكل 4: لا تخلط الردّ الذي يسمح بالخروج بالإشعار أنّ الخروج ملتزَم.

ثمّة حالات يمكنك فيها إعادة FALSE لرفض الخروج، لكنّ القاعدة احترام قصد المستخدم للخروج. تطبيق يرفض يُعرَض كتطبيق يمنع الإغلاق. للتطبيقات الطرفيّة والتطبيقات بلا نافذة مرئيّة قيود أيضاً: في تكوين عاديّ، تطبيق لا يستجيب خلال 5 ثوانٍ يمكن إنهاؤه تلقائيّاً. عامل الحظر المعالجة الاستثنائيّة للقسم 4 ولا تستخدمه للحفظ العاديّ.6

3.2. نحو 5 ثوانٍ ليست ضماناً أنّك تستطيع إنهاء الحفظ

إن أخّرت الردّ بنحو 5 ثوانٍ في مرحلة WM_QUERYENDSESSION أو WM_ENDSESSION، يعرض النظام الشاشة التي تسرد التطبيقات التي تمنع الإغلاق، ويستطيع المستخدم اختيار فرض الإغلاق. بعد إنهاء قسريّ لا فرصة لإنهاء بقيّة الحفظ.6

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

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

الشكل 5: الاستعداد للخروج في ثوانٍ يحدث قبل إشعار الخروج.

3.3. في WinForms وWPF، افصل الحفظ عن التنظيف الذي لا يمكن التراجع عنه

في WinForms المقابل هو FormClosing مع CloseReason.WindowsShutDown؛ في WPF هو Application.SessionEnding. يمكن لـ WPF أيضاً تسجيله عبر سمة SessionEnding في XAML، ويمكن معالجته بتجاوز OnSessionEnding.

غير أنّ كليهما أحداث مرحلة استعلام. ما يُسمَح به هنا على الأكثر «حفظ لقطة متكافئ»: غير ضارّ إن أُلغيَ الخروج، وينتج النتيجة نفسها مهما تكرّر. العمل الممكن فقط بعد التزام الخروج، كالفصل، يُؤدَّى باستقبال WM_ENDSESSION مع wParam=TRUE في WndProc لـ WinForms أو خطّاف WPF.

تقسيم معالجة الخروج في WinForms وWPFيحفظ FormClosing وSessionEnding لقطة آمنة حتّى إن أُلغيَ الخروج، ويؤدّي خطّاف على WM_ENDSESSION مع TRUE التنظيف الذي لا يمكن التراجع عنهمرحلة الاستعلامFormClosingSessionEndingحفظ لقطة متكافئWM_ENDSESSION مع TRUEاستقبل في WndProc أو خطّافعمل بعد الالتزام كالفصل

الشكل 6: لا تحشر عملاً بعد الالتزام في أحداث الإطار.

المثالان التاليان هما الجزء الذي يحفظ حالة العمل فقط في مرحلة الاستعلام. عيّنات الشيفرة مقتطفات تنفيذ؛ محتويات دالّة الحفظ، ومعالجة الفشل، وتسجيل الأحداث، وما شابه تعود إلى التطبيق.

// WinForms: يُستدعى FormClosing أيضاً عند الإغلاق وعند الخروج من الجلسة
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
    if (e.CloseReason == CloseReason.WindowsShutDown)
    {
        // احفظ لقطة idempotent فقط. لا تعرض حواراً.
        // لا تضبط e.Cancel = true (رفضاً) أيضاً.
        SaveWorkingStateToTempFile();
        return;
    }

    // في الحالات العاديّة، كإغلاق المستخدم بزرّ X، يجوز التأكيد هنا
}
// WPF: App.xaml.cs
protected override void OnSessionEnding(SessionEndingCancelEventArgs e)
{
    base.OnSessionEnding(e);

    // يمكن التمييز بين ReasonSessionEnding.Logoff / Shutdown،
    // لكن الأساس تشغيل حفظ اللقطة نفسه لكليهما
    SaveWorkingStateToTempFile();

    // لا تضبط e.Cancel = true إلا لسبب وجيه جدّاً
}

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

4. احظر مؤقّتاً، وفقط لعمل لا يمكن مقاطعته

4.1. تسجيل سبب ورفض الخروج عملان منفصلان

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

لاحظ مع ذلك أنّ تسجيل سلسلة سبب وحدها لا توقف الإغلاق. اجمعها مع علم «محميّ»، وفقط بينما هو مضبوط، أعد FALSE إلى WM_QUERYENDSESSION. عندما ينتهي العمل، امسح السبب وعلم الحماية كليهما.

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

الشكل 7: تسجيل سبب وحده ليس رفضاً، وحتّى تنفيذ الرفض لا يمنع إغلاقاً قسريّاً.

4.2. أبقِ مؤشّر الواجهة قادراً على استقبال طلب الخروج

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

[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);

// استدعِ من المؤشّر الذي أنشأ النافذة الرئيسيّة (الاستدعاءات من مؤشّرات أخرى تفشل)
_criticalOperationInProgress = true;
ShutdownBlockReasonCreate(this.Handle, "جارٍ كتابة بيانات القياس إلى ملف");
try
{
    // نفِّذ العمل الذي لا يمكن مقاطعته على مؤشّر عامل. التشغيل المتزامن على
    // مؤشّر الواجهة يوقف مضخّة الرسائل، ويُفرَض استمرار التطبيق كـ
    // «لا يستجيب» قبل أن تعمل شيفرة رفض WM_QUERYENDSESSION أدناه
    await Task.Run(() => WriteMeasurementData());
}
finally
{
    ShutdownBlockReasonDestroy(this.Handle);
    _criticalOperationInProgress = false;
}

// إضافةً إلى ذلك، أعد FALSE إلى WM_QUERYENDSESSION فقط أثناء الحماية، للرفض
protected override void WndProc(ref Message m)
{
    const int WM_QUERYENDSESSION = 0x0011;
    if (m.Msg == WM_QUERYENDSESSION && _criticalOperationInProgress)
    {
        m.Result = IntPtr.Zero;   // رفض. تُعرَض سلسلة السبب المسجَّلة في واجهة ملء الشاشة
        return;
    }
    base.WndProc(ref m);
}

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

حتّى حينها، يستطيع المستخدم اختيار فرض الإغلاق. ثمّة مسارات خروج قسريّ مثل ENDSESSION_CRITICAL أيضاً، لذا لا تجعل القدرة على الحظر أبداً مقدّمة لسلامة البيانات. الاستعداد للحالة التي لم تستطع إيقافها هو تصميم الحفظ والاستعادة في القسم 8.6

5. التطبيقات الطرفيّة و.NET ── افحص الشروط التي تصل الإشعارات تحتها

5.1. الإشعارات والمهلات المستقبَلة عبر SetConsoleCtrlHandler

في تطبيق طرفيّ، تصل إشارات التحكّم إلى المعالج المسجَّل بـ SetConsoleCtrlHandler. بخلاف معالجة رسائل الواجهة، يعمل المعالج على مؤشّر منفصل.2

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

إنهاء عمليّة قسريّاً من علامة تبويب «التفاصيل» في مدير المهام وما شابه إنهاء فوريّ بلا إشعار، وهو خارج هذا الجدول.2

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

شروط استقبال إشعارات خروج الطرفيّةطرفيّة تفاعليّة تستطيع استقبال إشعار الإغلاق لكن لا تستطيع توقّع إشعارات خروج أو إغلاق، وحتّى خدمة تحتاج مسار إشعار مختلفاً متى حمّلت مكتبات الواجهةجلسة تفاعليّةخدمةلانعمعمليّة طرفيّةالإغلاق يذهب إلى معالج التحكّمانتظار LOGOFF أو SHUTDOWN؟لا تعتمد على هذا الإشعارحمّلت مكتبات الواجهة؟عالِج إشارة التحكّم المعنيّةاستقبل عبر نافذة مخفيّة

الشكل 8: لا تعامل معالجة إغلاق الطرفيّة كمعالجة إغلاق.

المقتطف التالي يستعدّ لـ Ctrl+C وإغلاق الطرفيّة. يتمسّك بالمفوَّض كي لا يجمعه GC، وبعد تنظيف قصير يتقدّم إلى المعالج الافتراضيّ. تسجيل هذا وحده لا يضمن إشعارات إغلاق لتطبيق تفاعليّ.

// تطبيق طرفيّ: تنظيف عند Ctrl+C وإغلاق الطرفيّة
[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;  // احتفظ بمرجع كي لا يجمعه GC

static bool OnCtrlEvent(int ctrlType)
{
    // نفِّذ فقط تنظيفاً ينتهي خلال 5 ثوانٍ
    FlushAndCloseDataFile();
    return false;   // تقدَّم إلى المعالج الافتراضيّ؛ تنتهي العمليّة
}

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

5.2. من .NET 10 فصاعداً، لا تضع التنظيف فقط في ProcessExit

من .NET 10، لم يعد وقت التشغيل يوفّر معالجاً افتراضيّاً لـ CTRL_CLOSE_EVENT / CTRL_SHUTDOWN_EVENT. بلا معالج من عندك أو من مكتبة أعلى، تنهي معالجة نظام التشغيل الافتراضيّة العمليّة، وعلى هذا المسار لا يُطلَق لا AppDomain.ProcessExit ولا AssemblyLoadContext.Unloading.4

هذا تغيير للسلوك الافتراضيّ لإشارات الإنهاء الخارجيّة. لا يعني أنّ ProcessExit لم يعد يُطلَق عند خروج عاديّ كالعودة من Main.

إزالة الاعتماد على معالج الإنهاء الافتراضيّ لـ .NETوصل وقت التشغيل الأقدم الإشارات المعنيّة بـ ProcessExit، لكن من .NET 10 لا يُوفَّر معالج افتراضيّ، لذا استخدم مسار الإشعار لنموذج التطبيقلانعمإشارة CLOSE أو SHUTDOWNالمعالجة الافتراضيّة لوقت التشغيل الأقدميُطلِق ProcessExit وما شابهلا معالجة افتراضيّة من .NET 10 فصاعداًهل يعالجه التطبيق؟يُنهى بمعالجة نظام التشغيل الافتراضيّةتنظيف يناسب النموذج

الشكل 9: ميّز خروجاً عاديّاً عن إنهاء بإشارة خارجيّة، ووفِّر المعالج الذي يحتاجه نموذج تطبيقك.

ركِّز التنظيف حيث يناسب نموذج التطبيق. للواجهة، تلك أحداث القسم 3 والإشعار الملتزَم؛ لـ Generic Host، IHostApplicationLifetime وBackgroundService.StopAsync؛ لتطبيق طرفيّ بسيط، SetConsoleCtrlHandler أو PosixSignalRegistration. عند استخدام الأخير، اختر الإشارات التي تقابل مسارات الإنهاء التي تستهدفها، مثل SIGINT وSIGTERM وSIGHUP.4

في Generic Host، اضبط HostOptions.ShutdownTimeout صراحة. غير أنّ إعداد جانب الـ Host وحده لا يمدِّد المهلة الخارجيّة للواجهة أو الطرفيّة أو SCM. حتّى إن لم يكن لـ Ctrl+C مهلة صريحة، ما زلت تحتاج الاستعداد لفقدان الطاقة ومسارات إنهاء أخرى. في كلّ نموذج السياسة نفسها: أبقِ الحالة محفوظة وأبقِ العمل بعد الإشعار صغيراً.

معالجة إيقاف الـ Host ومهلة الخروج الخارجيّةيُركَّز إيقاف Generic Host في StopAsync، لكن ضبط مهلة HostOptions لا يمدِّد مهلة خروج نظام التشغيل أو SCM، لذا افحص كليهماطلب خروج من نظام التشغيل أو SCMمسار إيقاف الـ Hostنظِّف في StopAsyncاضبط زمن إيقاف جانب الـ Hostالمهلة الخارجيّة موجودة على حدةقِس ما إذا ينتهي بسرعة

الشكل 10: افحص حدود زمن جانب الـ Host وجانب نظام التشغيل منفصلين، ولا تكتفِ بتمديد الإعداد.

6. خدمات Windows ── عُد من معالج التحكّم فوراً

6.1. الفرق بين SHUTDOWN وPRESHUTDOWN

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

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

الشكل 11: الإشعار مبكّراً والانتظار شيئان منفصلان؛ لـ PRESHUTDOWN أيضاً موعد مضبوط.

تُضبَط مهلة PRESHUTDOWN بـ ChangeServiceConfig2(SERVICE_CONFIG_PRESHUTDOWN_INFO). الافتراضيّ 10 ثوانٍ من Windows 10 Creators Update (بناء 15063) فصاعداً و3 دقائق قبل ذلك. إن افترضت «PRESHUTDOWN يشتري دائماً 3 دقائق»، فلن تحصل على الزمن الذي توقّعته.9

لأنّ PRESHUTDOWN يوقف إغلاق النظام كلّه، استخدمه فقط عندما يكون لازماً حقّاً. إعادة كتابة WaitToKillServiceTimeout العاديّ من جانب الخدمة لتمديده غير موصى بها أيضاً.3

6.2. افصل استقبال الإشعار عن الإيقاف الفعليّ

يجب أن يعود معالج التحكّم خلال 30 ثانية، لكن بدل التفكير في ذلك كـ 30 ثانية يجوز لك استخدامها، هيكله لـ يشير إلى الإيقاف ويعود فوراً. سلِّم العمل الطويل إلى مؤشّر آخر وأبلغ SERVICE_STOP_PENDING.3

// خدمة Win32: اقبل PRESHUTDOWN واترك عمل الإيقاف لعامل
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);   // أشعِر العامل بالتوقّف وعُد فوراً
        return NO_ERROR;
    }
    return ERROR_CALL_NOT_IMPLEMENTED;
}

// جانب العامل: إن طال التنظيف عن wait hint، واصل الإبلاغ بـ
// SERVICE_STOP_PENDING دوريّاً مع زيادة dwCheckPoint. يحكم SCM من
// wait hint ونقطة التحقّق المتقدِّمة أنّ الخدمة «ما زالت حيّة وتتقدّم». إن توقّفت
// التقارير تُعامَل معلَّقة ويمكن أن يمضي الإغلاق. أبلغ دائماً SERVICE_STOPPED عند الاكتمال

في جانب العامل، إن استغرق التنظيف أطول من تلميح الانتظار، واصل الإبلاغ بـ SERVICE_STOP_PENDING مع تقديم dwCheckPoint. إن توقّفت تقارير التقدّم، يمكن الحكم على الخدمة معلَّقة. الإبلاغ بـ SERVICE_STOPPED عند الاكتمال جزء من معالجة الإيقاف.39

تقسيم العمل بين معالج تحكّم الخدمة والعامليبلّغ معالج التحكّم إيقافاً معلَّقاً، يُشعر العامل، ويعود فوراً؛ ينفّذ العامل التنظيف وإبلاغ التقدّم ويبلّغ أخيراً التوقّفمعالج التحكّمأبلغ STOP_PENDINGأشعِر العامل بالتوقّفيعود المعالج فوراًينظِّف العاملأبلغ التقدّم إن طالأبلغ STOPPED عند الاكتمال

الشكل 12: لا تحجب المؤشّر الذي يستقبل الإشعار بمعالجة إيقاف طويلة.

6.3. استطع الإنهاء بسرعة حتّى إن توقّفت تبعيّة أوّلاً

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

هذه السياسة تهمّ أيضاً في علاقتها بـ UPS. كلّما مدَّدت خدمة انتظارها، صار إكمال إغلاق نظام التشغيل كلّه قبل نفاد البطاريّة أصعب. بدل زيادة المهلة، قلِّل العمل عند الإيقاف بالحفظ عند كلّ معلم.3

عندما يعمل Worker Service في .NET تحت UseWindowsService، تُحوَّل STOP / SHUTDOWN إلى إيقاف Host، ممّا يؤدّي إلى BackgroundService.StopAsync. التنفيذ القياسيّ، وقت كتابة المقال الأصليّ، لا يقبل PRESHUTDOWN، لذا إن احتجته عليك توسيع تنفيذ المعالج. سياسة ضبط HostOptions.ShutdownTimeout صراحة وإنهاء StopAsync نفسه في ثوانٍ هي نفسها. للتنفيذ الكلّيّ، انظر كيفيّة إنشاء خدمات Windows وتشغيلها.

7. الاستعادة بعد إعادة تشغيل ── صفِّ التسجيل، وبيانات الاستعادة، وتسجيل الدخول

7.1. RegisterApplicationRestart يسجّل مسار الاستعادة مسبقاً

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

يمكنك تحديد وسائط سطر أوامر لإعادة التشغيل، كالملفّات التي كانت مفتوحة أو نقطة استعادة. غير أنّ التسجيل وحده لا يعني استعادة تلقائيّة من كلّ حالة.

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

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

7.2. ردّ نداء الاستعادة وARSO يلعبان أدواراً مختلفة

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

الحفظ وإشعار التقدّم في ردّ نداء الاستعادةيواصل ردّ نداء الاستعادة الذي يستدعيه WER استدعاء ApplicationRecoveryInProgress خلال فاصل النبض أثناء الحفظ، ويُشعر ApplicationRecoveryFinished عند الاكتمالليس بعدتمّسجِّل ردّ نداء الاستعادة مسبقاًانهيار؛ يستدعيه WERاحفظ البيانات قيد العملأبلغ التقدّم خلال فاصل النبضاكتمل الحفظ؟أبلغ اكتمال الاستعادةيمكن قطعه إن توقّفت التقارير

الشكل 13: لا تحفظ فحسب؛ أبلغ WER بالتقدّم والاكتمال.

استبدال ملفّات قيد الاستخدام أثناء تحديث وإعادة التشغيل هو عمل Restart Manager. مشروح في كيفيّة استبدال EXE أو DLL قيد الاستخدام.

الآليّة التي تعيد جلسة المستخدم بعد إعادة تشغيل نظام تشغيل، من جهة أخرى، هي ARSO (تسجيل دخول تلقائيّ لإعادة تشغيل Winlogon). عندما يبدأ Windows Update إعادة تشغيل تلقائيّة، يحفظ بأمان بيانات اعتماد آخر مستخدم تفاعليّ ويضبط Autologon، وبعد إعادة التشغيل يسجّل دخول ذلك المستخدم ويقفل الشاشة.11

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

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

shutdown /g هو الأمر الذي يطلب إعادة تشغيل زائد استئناف التطبيقات المسجَّلة. قد يُعطَّل ARSO بسياسة منظَّمة مثل DisableAutomaticRestartSignOn، لذا افحصه مع متطلّبات الاستعادة بلا مراقبة. المعالجة الخلفيّة اللازمة دائماً أفضل تشغيلها كخدمة Windows من جعلها تعتمد على تسجيل دخول المستخدم التلقائيّ.11

8. فقدان طاقة بلا إشعار ── صمِّم الحفظ والتحميل زوجاً

8.1. ملفّ مؤقّت، وتبديل، ونسخة احتياطيّة، وتحقّق عند الإقلاع كمجموعة واحدة

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

الأساس الكتابة بالكامل إلى ملفّ مؤقّت على المجلّد نفسه، وإفراغه، ثمّ التبديل. يجمع ReplaceFile الخطوات التي تقابل الحفظ إلى ملفّ جديد، وتنحية الأصليّ، وإعادة التسمية، والحذف، ويحمل سمات كزمن الإنشاء وACL وتدفّقات البيانات البديلة. يجب أن يكون الملفّ المستبدَل، وملفّ الاستبدال، والنسخة الاحتياطيّة على المجلّد نفسه. يستدعي File.Replace في .NET هذه الواجهة.12

حفظ ملفّ والاستعادة عند الإقلاع التالياكتب إلى ملفّ مؤقّت على المجلّد نفسه، افرغ، بدِّل مع الاحتفاظ بنسخة احتياطيّة، ثمّ تحقّق من الملفّ الأوّليّ عند الإقلاع وارجع إلى النسخة الاحتياطيّة إن لزمنعملاملفّ مؤقّت على المجلّد نفسهاكتب إلى النهاية وافرغبدِّل؛ المحتويات القديمة تذهب إلى .bakالإقلاع التاليهل الملفّ الأوّليّ سليم؟اقرأ الملفّ الأوّليّارجع إلى النسخة الاحتياطيّة

الشكل 15: نفِّذ لا الطريقة الآمنة للكتابة فحسب بل أيضاً طريقة القراءة عندما يكون الملفّ مكسوراً.

// النمط القياسي لحفظ الإعدادات والبيانات: اكتب إلى ملفّ مؤقّت ثمّ بدِّل، مع الإبقاء على المحتوى القديم
public static void SaveAtomically(string path, string content)
{
    string dir = Path.GetDirectoryName(path)!;
    string tmp = Path.Combine(dir, Path.GetRandomFileName());  // أنشئه على المجلّد نفسه

    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. يكتب مخازن نظام التشغيل
                                           // إلى القرص (حدود الذاكرة المؤقّتة في جانب الجهاز في القسم 8.2)
        }

        if (File.Exists(path))
            File.Replace(tmp, path, path + ".bak");  // يستدعي ReplaceFile. يبقي المحتوى القديم كـ .bak
        else
            File.Move(tmp, path);
    }
    catch
    {
        // إن فشل في المنتصف، لا تترك الملفّ المؤقّت. إن واصلت الحفظات الدوريّة
        // الفشل، ستملأ النسخ الكاملة المجلّد تدريجيّاً
        try { File.Delete(tmp); } catch { /* فشل الحذف يتنازل للاستثناء الأصليّ */ }
        throw;
    }
}

رغم أنّ هذا المثال مسمّى SaveAtomically، فإنّه لا يضمن الذرّيّة عبر فقدان طاقة. في التشغيل العاديّ يترك إمّا الملفّ القديم الكامل أو الجديد الكامل قابلاً للقراءة، لكنّ ReplaceFile عمليّة مساحة أسماء متعدّدة الخطوات، وذرّيّتها ضدّ فقدان طاقة مفاجئ غير مضمونة بالمواصفة. لذلك بالضبط تحتفظ بـ .bak وتنفِّذ روتين التحميل الذي يتحقّق من الملفّ الأوّليّ عند الإقلاع ويرجع إلى النسخة الاحتياطيّة إن كان مكسوراً.12

الـ catch في المثال موجود كي لا تتراكم الملفّات المؤقّتة عند إخفاقات عاديّة وتملأ المجلّد. لا يتوقّع تشغيل هذه الشيفرة لحظة فقدان طاقة. لسجلّات الإلحاق فقط وCSV، لا تطبِّق نهج تبديل الملفّ كلّه كما هو؛ استخدم تنسيقاً يحسب كيف ينكسر، مثل «اكتب سطراً واحداً لكلّ سجلّ وتجاهل آخر سطر فاسد عند التحميل».

8.2. ميّز نجاح WriteFile عن بلوغ القرص

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

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

الحدّ بين نجاح الكتابة والثباتكتابة عاديّة تبلغ التخزين من ذاكرة نظام التشغيل المؤقّتة بتأخير؛ إفراغ أو كتابة فوريّة يدفعها إلى الالتزام، لكنّ قيود الذاكرة المؤقّتة في جانب الجهاز تبقىينجح WriteFileقد يكون في ذاكرة نظام التشغيل المؤقّتةكتابة كسولةافرغ عند نقطة تحقّقيُطبَّق على التخزينحدود الذاكرة المؤقّتة في جانب الجهاز

الشكل 16: لا تعامل نجاح الواجهة، والتزام ذاكرة نظام التشغيل المؤقّتة، ومقاومة فقدان الطاقة كشيء واحد.

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

8.3. استخدم UPS لتحويل فقدان طاقة إلى إغلاق مخطَّط

دور UPS ليس إلغاء الانقطاعات بل تحويل فقدان طاقة بلا إشعار إلى إغلاق مخطَّط بإشعار. الشرط الذي تحتاجه هذه العلاقة:

زمن تشغيل بطاريّة UPS > زمن كشف التحويل + تنظيف التطبيقات والخدمات + إكمال إغلاق نظام التشغيل

يُبلَّغ التحويل بين طاقة التيّار المتردّد والبطاريّة، والشحن المتبقّي المنخفض، عبر PBT_APMPOWERSTATUSCHANGE. تستقبله واجهة عبر WM_POWERBROADCAST؛ تعلن خدمة SERVICE_ACCEPT_POWEREVENT ثمّ تستقبل SERVICE_CONTROL_POWEREVENT في HandlerEx. لا تُسلَّم WM_POWERBROADCAST إلى معالج تحكّم خدمة.14

بعد استقباله، افحص ACLineStatus وBatteryLifePercent بـ GetSystemPowerStatus، وتقدّم إلى تعليق القياس، والحفظ، وطلب الإغلاق.14

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

الشكل 17: ناسب لا التطبيق فحسب بل الزمن حتّى يخرج نظام التشغيل ضمن زمن تشغيل UPS.

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

9. التحقّق ── أكِّد الإشعارات، والتوقيت، ونتائج الاستعادة

9.1. أعد إنتاج مسارات الخروج على جهاز اختبار، لا في الإنتاج

لا تجرِّب أشياء على حاسوب الجهاز الإنتاجيّ أوّلاً؛ استخدم جهاز اختبار أو آلة افتراضيّة مثل Hyper-V. على آلة افتراضيّة، خذ نقطة تحقّق مسبقاً وكرِّر الاختبارات ببيانات اختبار يمكنك تحمّل فقدانها.

العمليّة التي تجرّبها ما تؤكّده
الخروج WM_QUERYENDSESSION ← WM_ENDSESSION للواجهة ومعالجة الحفظ. ليست بديلاً للتحقّق من إيقاف خدمة
shutdown /s /t 0 السلوك عند إغلاق كامل
shutdown /s /hybrid /t 0 السلوك الهجين في تكوين يستخدم بدء التشغيل السريع
shutdown /r /t 0 إعادة تشغيل بإقلاع كامل، والاستعادة بعدها
إطفاء الآلة الافتراضيّة ما إذا يستعيد الإقلاع التالي حتّى عندما يتوقّف نظام تشغيل الضيف بلا إشعار
فقدان طاقة على عتاد مكافئ للإنتاج الصمود بما في ذلك التخزين الفيزيائيّ والمتحكّم

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

توسيع التحقّق من الإغلاق على مراحلجرِّب إشعارات الواجهة وكلّ عمليّة خروج في بيئة الاختبار، أكِّد الاستعادة بعد توقّف مفاجئ على آلة افتراضيّة، ثمّ تحقّق من صمود فقدان الطاقة بما في ذلك التخزين على عتاد مكافئ للإنتاججهِّز جهاز الاختبار والبياناتجرِّب مسارات الإشعار وعمليّات الخروجقِس زمن التنظيفأكِّد الاستعادة بعد توقّف آلة افتراضيّة مفاجئتحقّق على عتاد حقيقيّ بما في ذلك التخزينأكِّد البيانات والاستعادة عند الإقلاع التالي

الشكل 18: اختبر لا ما إذا يخرج التطبيق بشكل عاديّ فحسب بل أيضاً ما يستطيع استعادته بعد توقّف مفاجئ.

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

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

9.2. اعزل ما حدث ليلاً من سجلّ الأحداث

في سجلّ نظام Windows، يسجّل معرّف الحدث 1074 العمليّة التي بدأت الإغلاق، والمستخدم، والسبب. لإغلاق غير متوقَّع، يُسجَّل 41 (Kernel-Power) أو 6008 عند الإقلاع التالي.15

# افحص السجلّ الحديث لأحداث الإغلاق
Get-WinEvent -FilterHashtable @{ LogName = 'System'; Id = 1074, 6008, 41 } -MaxEvents 20 |
    Select-Object TimeCreated, Id, ProviderName, Message |
    Format-List

إن أظهر 1074 إعادة تشغيل Windows Update وما زالت البيانات فاسدة، حقِّق أوّلاً معالجة إشعار الخروج ومسار الحفظ. من جهة أخرى، لا تستنتج فقدان طاقة من 41 أو 6008 وحدهما. يشيران إلى إنهاء غير متوقَّع، وشاشة زرقاء أو إعادة ضبط قسريّة مرشّحان أيضاً.15

اختيار ما تحقّقه من سجلّ أحداث الإغلاقأكِّد العمليّة المبادر والسبب من 1074، وعامل 41 و6008 كأدلّة على إنهاء غير متوقَّع لتقابلها بمعلومات محيطة كرمز فحص الخطأ والتفريغاتسجلّ أحداث النظام1074: العمليّة المبادر والسببحقِّق معالجة الإشعار ومسار الحفظ41 و6008: إنهاء غير متوقَّعافحص BugcheckCode والتفريغاتاعزل الانهيار وفقدان الطاقة وما شابه

الشكل 19: 41 و6008 ليسا دليلاً على فقدان طاقة نفسه؛ إنّهما مدخل إلى تحقيق إضافيّ.

BugcheckCode غير صفريّ في الحدث 41 دليل على انهيار. إن كان 0 ولا تفريغ ذاكرة، يُشتبَه بفقدان طاقة، لكن قرِّر بمقابلته بالمعلومات المحيطة. إن تبيّن فقدان طاقة، ركِّز على تصميم الحفظ وUPS في القسم 8؛ إن كان خروجاً مخطَّطاً، ركِّز على الإشعارات والتنظيف في الأقسام 3 إلى 6.

10. الخلاصة ── صمِّم حتّى الإقلاع التالي، لا معالجة الخروج فحسب

معالجة الإغلاق ليست عن معالج حدث يعمل مرّة عند الخروج. ما يهمّ جعل الحفظ الروتينيّ ← تنظيفاً قصيراً ← التحقّق والاستعادة عند الإقلاع التالي كلاً واحداً مستمرّاً.

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

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

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

الشكل 20: لا تفصل تنفيذ حدث الخروج عن الحفظ الروتينيّ والاستعادة عند الإقلاع التالي.

أخيراً، جرِّب مسارات الإشعار والزمن المطلوب على جهاز اختبار، وأكِّد الاستعادة بعد توقّف مفاجئ أيضاً. في التحقيق بعد الواقعة، استخدم 1074 / 41 / 6008 أدلّة لعزل خروج مخطَّط عن إنهاء غير متوقَّع.

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

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

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

تتعامل شركة كومورا سوفت ذ.م.م. مع تصميم وتنفيذ إجراءات مضادّة للإغلاق وفقدان الطاقة لحواسيب الأجهزة والتطبيقات طويلة التشغيل، وتحقيق السبب الجذريّ لفساد بيانات وإخفاقات «كان متوقّفاً في الصباح» تبدأ من إعادة تشغيل 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

  2. 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 ↩4

  3. 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 ↩6 ↩7

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

  5. 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

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

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

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

  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

  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 صراحة؛ وتخزين بيانات تعريف نظام الملفّات دائماً مؤقّتاً، لذا يتطلّب التزام البيانات الوصفيّة إفراغاً أو كتابة فوريّة. ↩ ↩2

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

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

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

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

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

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

مشكلة لم يصلحها «إيقاف التشغيل» اختفت بعد «إعادة التشغيل». لماذا؟
على أنظمة تشغيل العميل من 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، ويكشف التحويل إلى البطاريّة، وينتقل إلى إغلاق آمن.

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

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

غو كومورا

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

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

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