إغلاق 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.
flowchart TB
accTitle: تصميم حفظ مشترك لإشعارات الخروج وفقدان الطاقة
accDescr: الحفظ الروتينيّ يبقي الفرق غير المحفوظ صغيراً؛ مع إشعار يتبع تنظيف قصير، وبدونه تُتحقَّق البيانات المحفوظة وتُستعاد قبل استئناف العمل
daily["احفظ عند كلّ معلم"] --> event{"هل ثمّة إشعار عند الخروج؟"}
event -->|"نعم"| close["احفظ الفرق المتبقّي فقط واخرج"]
event -->|"لا"| lost["لا تنظيف ممكناً في المكان"]
close --> boot["تحقّق واستعد عند الإقلاع التالي"]
lost --> boot
boot --> resume["استأنف العمل"]
الشكل 1: بدل العمل بجدّ فقط عند الإشعار، اجعل الحفظ الروتينيّ حتّى الإقلاع التالي عمليّة واحدة مستمرّة.
في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 14، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle
2. ميّز أوّلاً ── الخروج، والإغلاق، وإعادة التشغيل، وفقدان الطاقة
2.1. انظر إلى جلسة المستخدم والنواة منفصلين
تقسيم «ما ينتهي» إلى جزأين يجعل المعالجة أسهل تنظيماً. جلسة المستخدم هي النطاق الذي تشغّل فيه تطبيقات ذلك المستخدم. النواة والتعريفات، من جهة أخرى، تنتميان إلى جانب نظام التشغيل وتظلان تعملان بعد خروج المستخدم.
| العمليّة | جلسة المستخدم | النواة والتعريفات | الإشعارات |
|---|---|---|---|
| الخروج | تنتهي | تظلّ تعمل | تحصل الواجهة على استعلام الخروج والإشعار الملتزَم. الخدمات لا تتوقّف عند الخروج |
| إغلاق مع بدء تشغيل سريع مفعّل | تنتهي | تحفظ حالة إسباتها إلى hiberfil.sys |
إشعارات إنهاء جلسة الواجهة وإشعار الإغلاق للخدمات |
| إعادة التشغيل | تنتهي | تنتهي بالكامل وتقوم بإقلاع كامل في المرّة التالية | كما أعلاه |
| فقدان طاقة مفاجئ | تُفقَد فوراً | تُفقَد فوراً | لا إشعار |
في WM_QUERYENDSESSION للواجهة، تشير بتّة ENDSESSION_LOGOFF في lParam إلى خروج. إن كان lParam 0 فهو إغلاق أو إعادة تشغيل، ولا يمكن تمييز الاثنين. عامل lParam قناع بتّات.1
لبيانات التطبيق غير المحفوظة، يستدعي الخروج والإغلاق الاستعداد نفسه. لا تقسمهما إلى «لا حاجة للحفظ عند الخروج»؛ وجِّه كليهما إلى روتين حفظ مشترك. غير أنّ ذلك لا يجعل شروط الإشعار للتطبيقات الطرفيّة والخدمات متطابقة؛ افحص المسارات في القسمين 5 و6 لكلّ منهما.
flowchart TB
accTitle: فكِّر في الخروج وخروج النظام منفصلين
accDescr: يخرج الخروج تطبيقات المستخدم بينما تظلّ الخدمات تعمل؛ إغلاق نظام أو إعادة تشغيل يوقف الخدمات أيضاً؛ فقدان طاقة لا يُشعر أيّاً منهما
signout["خروج"] --> user["تخرج تطبيقات المستخدم"]
signout -.-> alive["الخدمات تظلّ تعمل"]
system["إغلاق أو إعادة تشغيل"] --> user
system --> svc["تُوقَف الخدمات أيضاً"]
power["فقدان طاقة"] --> none["لا إشعار لأيّ منهما"]
الشكل 2: خروج يتيح لك تمرين معالجة خروج الواجهة، لكنّه لا يُعدّ اختباراً لمعالجة إيقاف خدمة.
2.2. لماذا «الإغلاق لا يصلحه، لكنّ إعادة التشغيل تفعل»
على أنظمة تشغيل العميل من Windows 8 فصاعداً، يكون بدء التشغيل السريع مفعّلاً افتراضيّاً على كثير من الحواسيب التي تدعم الإسبات. في هذا التكوين يُخرِج الإغلاق المستخدم، لكنّ حالة النواة وتعريفات الأجهزة تُحفَظ في ملفّ الإسبات وتُستعاد عند الإقلاع التالي. قطع الطاقة لا يعني بالضرورة أنّ حالة نظام التشغيل كلّها صُفِّرت.5
هذا السلوك مشروط. حيث يُعطَّل الإسبات (powercfg /hibernate off)، وحيث يُوقَف بدء التشغيل السريع بسياسة أو في خيارات الطاقة، وعلى Windows Server، تحصل على الإغلاق الكامل التقليديّ. افحص إعدادات خيارات الطاقة، واستخدم powercfg /a لفحص ما إذا كان بدء التشغيل السريع متاحاً.
«إعادة التشغيل»، بالمقابل، تنفّذ دائماً دورة إقلاع كاملة. في إجراء لعزل مشكلة تعريف، اكتب «إعادة التشغيل» صراحة لا «أطفئه وشغِّله من جديد».5
flowchart TB
accTitle: بدء التشغيل السريع مقابل إقلاع كامل
accDescr: إغلاق مع بدء تشغيل سريع مفعّل يحفظ حالة النواة والتعريفات ويستعيدها، بينما إغلاق كامل أو إعادة تشغيل يهيّئ كلّ شيء بإقلاع كامل
s["إيقاف التشغيل"] --> q{"استخدم بدء التشغيل السريع؟"}
q -->|"نعم"| save["أسبِت النواة والتعريفات"]
save --> restore["استعد الحالة عند الإقلاع التالي"]
q -->|"لا"| full["إغلاق كامل"]
full --> boot["هيّئ بإقلاع كامل في المرّة التالية"]
r["إعادة التشغيل"] --> boot
الشكل 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
flowchart TB
accTitle: استعلام الواجهة ونتيجة الخروج
accDescr: حتّى بعد إعادة TRUE إلى WM_QUERYENDSESSION يمكن لتطبيق آخر إلغاء الخروج، لذا يعمل التنظيف بعد الالتزام فقط عندما يكون wParam لـ WM_ENDSESSION TRUE
query["WM_QUERYENDSESSION"] --> reply["أعد TRUE فوراً، كقاعدة"]
reply --> result["WM_ENDSESSION"]
result --> yes{"هل wParam TRUE؟"}
yes -->|"نعم"| cleanup["تنظيف بعد الالتزام"]
yes -->|"لا"| running["أُلغيَ الخروج؛ ظلّ يعمل"]
الشكل 4: لا تخلط الردّ الذي يسمح بالخروج بالإشعار أنّ الخروج ملتزَم.
ثمّة حالات يمكنك فيها إعادة FALSE لرفض الخروج، لكنّ القاعدة احترام قصد المستخدم للخروج. تطبيق يرفض يُعرَض كتطبيق يمنع الإغلاق. للتطبيقات الطرفيّة والتطبيقات بلا نافذة مرئيّة قيود أيضاً: في تكوين عاديّ، تطبيق لا يستجيب خلال 5 ثوانٍ يمكن إنهاؤه تلقائيّاً. عامل الحظر المعالجة الاستثنائيّة للقسم 4 ولا تستخدمه للحفظ العاديّ.6
3.2. نحو 5 ثوانٍ ليست ضماناً أنّك تستطيع إنهاء الحفظ
إن أخّرت الردّ بنحو 5 ثوانٍ في مرحلة WM_QUERYENDSESSION أو WM_ENDSESSION، يعرض النظام الشاشة التي تسرد التطبيقات التي تمنع الإغلاق، ويستطيع المستخدم اختيار فرض الإغلاق. بعد إنهاء قسريّ لا فرصة لإنهاء بقيّة الحفظ.6
الإجراء المضادّ الحفظ روتينيّاً وتقليل الفرق عند الخروج. أوقف حالة العمل غير المحفوظة في موضع مؤقّت واستعدها عند الإقلاع التالي. لا تصمِّم التطبيق لعرض حوار تأكيد أثناء الإغلاق والانتظار. عامل تأكيد الخروج العاديّ وطلب خروج من نظام التشغيل شيئين منفصلين.1
flowchart TB
accTitle: أبقِ العمل عند الخروج صغيراً
accDescr: تصميم يحفظ روتينيّاً يترك فرقاً صغيراً عند الخروج، بينما تصميم يتراكم في الذاكرة حتّى الخروج لا يناسب المهلة القصيرة ويخاطر بالفقدان عبر إنهاء قسريّ
good["احفظ روتينيّاً"] --> small["الفرق المتبقّي عند الخروج صغير"]
small --> fast["اخرج بتنظيف قصير"]
bad["تراكم في الذاكرة حتّى الخروج"] --> large["احفظ كلّ شيء عند الخروج"]
large --> risk["مهلة قصيرة جدّاً؛ خطر إنهاء قسريّ"]
الشكل 5: الاستعداد للخروج في ثوانٍ يحدث قبل إشعار الخروج.
3.3. في WinForms وWPF، افصل الحفظ عن التنظيف الذي لا يمكن التراجع عنه
في WinForms المقابل هو FormClosing مع CloseReason.WindowsShutDown؛ في WPF هو Application.SessionEnding. يمكن لـ WPF أيضاً تسجيله عبر سمة SessionEnding في XAML، ويمكن معالجته بتجاوز OnSessionEnding.
غير أنّ كليهما أحداث مرحلة استعلام. ما يُسمَح به هنا على الأكثر «حفظ لقطة متكافئ»: غير ضارّ إن أُلغيَ الخروج، وينتج النتيجة نفسها مهما تكرّر. العمل الممكن فقط بعد التزام الخروج، كالفصل، يُؤدَّى باستقبال WM_ENDSESSION مع wParam=TRUE في WndProc لـ WinForms أو خطّاف WPF.
flowchart TB
accTitle: تقسيم معالجة الخروج في WinForms وWPF
accDescr: يحفظ FormClosing وSessionEnding لقطة آمنة حتّى إن أُلغيَ الخروج، ويؤدّي خطّاف على WM_ENDSESSION مع TRUE التنظيف الذي لا يمكن التراجع عنه
notify["مرحلة الاستعلام"] --> forms["FormClosing"]
notify --> wpf["SessionEnding"]
forms --> snapshot["حفظ لقطة متكافئ"]
wpf --> snapshot
final["WM_ENDSESSION مع TRUE"] --> hook["استقبل في WndProc أو خطّاف"]
hook --> cleanup["عمل بعد الالتزام كالفصل"]
الشكل 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. عندما ينتهي العمل، امسح السبب وعلم الحماية كليهما.
flowchart TB
accTitle: تقسيم الأدوار في حظر خروج مؤقّت
accDescr: فقط أثناء سير عمل لا يمكن مقاطعته، اجمع تسجيل السبب مع رفض الاستعلام وامسح كليهما عند الاكتمال؛ إغلاق قسريّ من المستخدم ما زال لا يمكن منعه
start["يبدأ عمل لا يمكن مقاطعته"] --> reason["سجِّل السبب واضبط علم الحماية"]
reason --> work["عالِج على مؤشّر عامل"]
work --> done["أنهِ؛ امسح السبب والعلم"]
work -.-> request["يصل استعلام خروج في الأثناء"]
request --> refuse["أعد FALSE واعرض السبب"]
refuse --> choice{"قرار المستخدم"}
choice -->|"إلغاء"| keep["ظلّ يعمل"]
choice -->|"فرض"| terminate["يمكن إنهاؤه"]
الشكل 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
flowchart TB
accTitle: شروط استقبال إشعارات خروج الطرفيّة
accDescr: طرفيّة تفاعليّة تستطيع استقبال إشعار الإغلاق لكن لا تستطيع توقّع إشعارات خروج أو إغلاق، وحتّى خدمة تحتاج مسار إشعار مختلفاً متى حمّلت مكتبات الواجهة
console["عمليّة طرفيّة"] --> close["الإغلاق يذهب إلى معالج التحكّم"]
console --> q{"انتظار LOGOFF أو SHUTDOWN؟"}
q -->|"جلسة تفاعليّة"| no["لا تعتمد على هذا الإشعار"]
q -->|"خدمة"| dll{"حمّلت مكتبات الواجهة؟"}
dll -->|"لا"| signal["عالِج إشارة التحكّم المعنيّة"]
dll -->|"نعم"| window["استقبل عبر نافذة مخفيّة"]
الشكل 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.
flowchart TB
accTitle: إزالة الاعتماد على معالج الإنهاء الافتراضيّ لـ .NET
accDescr: وصل وقت التشغيل الأقدم الإشارات المعنيّة بـ ProcessExit، لكن من .NET 10 لا يُوفَّر معالج افتراضيّ، لذا استخدم مسار الإشعار لنموذج التطبيق
signal["إشارة CLOSE أو SHUTDOWN"] --> old["المعالجة الافتراضيّة لوقت التشغيل الأقدم"]
old --> event["يُطلِق ProcessExit وما شابه"]
signal --> modern["لا معالجة افتراضيّة من .NET 10 فصاعداً"]
modern --> own{"هل يعالجه التطبيق؟"}
own -->|"لا"| os["يُنهى بمعالجة نظام التشغيل الافتراضيّة"]
own -->|"نعم"| handle["تنظيف يناسب النموذج"]
الشكل 9: ميّز خروجاً عاديّاً عن إنهاء بإشارة خارجيّة، ووفِّر المعالج الذي يحتاجه نموذج تطبيقك.
ركِّز التنظيف حيث يناسب نموذج التطبيق. للواجهة، تلك أحداث القسم 3 والإشعار الملتزَم؛ لـ Generic Host، IHostApplicationLifetime وBackgroundService.StopAsync؛ لتطبيق طرفيّ بسيط، SetConsoleCtrlHandler أو PosixSignalRegistration. عند استخدام الأخير، اختر الإشارات التي تقابل مسارات الإنهاء التي تستهدفها، مثل SIGINT وSIGTERM وSIGHUP.4
في Generic Host، اضبط HostOptions.ShutdownTimeout صراحة. غير أنّ إعداد جانب الـ Host وحده لا يمدِّد المهلة الخارجيّة للواجهة أو الطرفيّة أو SCM. حتّى إن لم يكن لـ Ctrl+C مهلة صريحة، ما زلت تحتاج الاستعداد لفقدان الطاقة ومسارات إنهاء أخرى. في كلّ نموذج السياسة نفسها: أبقِ الحالة محفوظة وأبقِ العمل بعد الإشعار صغيراً.
flowchart TB
accTitle: معالجة إيقاف الـ Host ومهلة الخروج الخارجيّة
accDescr: يُركَّز إيقاف Generic Host في StopAsync، لكن ضبط مهلة HostOptions لا يمدِّد مهلة خروج نظام التشغيل أو SCM، لذا افحص كليهما
outer["طلب خروج من نظام التشغيل أو SCM"] --> host["مسار إيقاف الـ Host"]
host --> stop["نظِّف في StopAsync"]
stop --> inner["اضبط زمن إيقاف جانب الـ Host"]
outer -.-> limit["المهلة الخارجيّة موجودة على حدة"]
inner --> check["قِس ما إذا ينتهي بسرعة"]
limit --> check
الشكل 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 حتّى تتوقّف الخدمة أو تنتهي المهلة المضبوطة |
flowchart TB
accTitle: مراحل إشعار الخروج للخدمات
accDescr: يُشعر SCM أوّلاً الخدمات التي تقبل PRESHUTDOWN، ينتظر توقّفها أو انتهاء المهلة، ثمّ يتقدّم إلى إشعار SHUTDOWN العاديّ
start["يبدأ إغلاق النظام"] --> pre["أشعِر الخدمات التي تقبل PRESHUTDOWN"]
pre --> wait["انتظر التوقّف أو الموعد المضبوط"]
wait --> shut["أشعِر الخدمات التي تقبل SHUTDOWN"]
shut --> proceed["بعد المهلة، تقدّم إلى خروج النظام"]
الشكل 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
flowchart TB
accTitle: تقسيم العمل بين معالج تحكّم الخدمة والعامل
accDescr: يبلّغ معالج التحكّم إيقافاً معلَّقاً، يُشعر العامل، ويعود فوراً؛ ينفّذ العامل التنظيف وإبلاغ التقدّم ويبلّغ أخيراً التوقّف
handler["معالج التحكّم"] --> pending["أبلغ STOP_PENDING"]
pending --> signal["أشعِر العامل بالتوقّف"]
signal --> back["يعود المعالج فوراً"]
signal --> worker["ينظِّف العامل"]
worker --> report["أبلغ التقدّم إن طال"]
report --> stopped["أبلغ 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 عند الاكتمال. إن توقّفت إشعارات التقدّم، يمكن قطع معالجة الاستعادة.
flowchart TB
accTitle: الحفظ وإشعار التقدّم في ردّ نداء الاستعادة
accDescr: يواصل ردّ نداء الاستعادة الذي يستدعيه WER استدعاء ApplicationRecoveryInProgress خلال فاصل النبض أثناء الحفظ، ويُشعر ApplicationRecoveryFinished عند الاكتمال
reg["سجِّل ردّ نداء الاستعادة مسبقاً"] --> crash["انهيار؛ يستدعيه WER"]
crash --> save["احفظ البيانات قيد العمل"]
save --> progress["أبلغ التقدّم خلال فاصل النبض"]
progress --> completed{"اكتمل الحفظ؟"}
completed -->|"ليس بعد"| save
completed -->|"تمّ"| done["أبلغ اكتمال الاستعادة"]
progress -.-> timeout["يمكن قطعه إن توقّفت التقارير"]
الشكل 13: لا تحفظ فحسب؛ أبلغ WER بالتقدّم والاكتمال.
استبدال ملفّات قيد الاستخدام أثناء تحديث وإعادة التشغيل هو عمل Restart Manager. مشروح في كيفيّة استبدال EXE أو DLL قيد الاستخدام.
الآليّة التي تعيد جلسة المستخدم بعد إعادة تشغيل نظام تشغيل، من جهة أخرى، هي ARSO (تسجيل دخول تلقائيّ لإعادة تشغيل Winlogon). عندما يبدأ Windows Update إعادة تشغيل تلقائيّة، يحفظ بأمان بيانات اعتماد آخر مستخدم تفاعليّ ويضبط Autologon، وبعد إعادة التشغيل يسجّل دخول ذلك المستخدم ويقفل الشاشة.11
flowchart TB
accTitle: تسجيل إعادة التشغيل، وتسجيل الدخول، واستعادة البيانات
accDescr: مقابل تسجيل إعادة تشغيل مسبق، يتطلّب الانهيار موافقة المستخدم، وتطبيقات المستخدم بعد إعادة تشغيل نظام تشغيل تحتاج استعادة الجلسة عبر ARSO أو ما شابه واستعادة الحالة المحفوظة
reg["سجِّل لإعادة التشغيل قبل حدوث مشكلة"] --> crash["انهيار أو عدم استجابة"]
crash --> consent["احصل على موافقة المستخدم"]
consent --> app["أعد تشغيل التطبيق"]
reg --> update["إعادة تشغيل نظام تشغيل بالأعلام المطلوبة"]
update --> session["استعد الجلسة عبر ARSO أو ما شابه"]
session --> app
app --> data["اقرأ نقطة الاستعادة المحفوظة"]
session -.-> policy["افحص السياسة وشروط البدء"]
الشكل 14: وفِّر لا تسجيل إعادة تشغيل التطبيق فحسب بل أيضاً المسار الذي تعود به الجلسة وحالة العمل.
shutdown /g هو الأمر الذي يطلب إعادة تشغيل زائد استئناف التطبيقات المسجَّلة. قد يُعطَّل ARSO بسياسة منظَّمة مثل DisableAutomaticRestartSignOn، لذا افحصه مع متطلّبات الاستعادة بلا مراقبة. المعالجة الخلفيّة اللازمة دائماً أفضل تشغيلها كخدمة Windows من جعلها تعتمد على تسجيل دخول المستخدم التلقائيّ.11
8. فقدان طاقة بلا إشعار ── صمِّم الحفظ والتحميل زوجاً
8.1. ملفّ مؤقّت، وتبديل، ونسخة احتياطيّة، وتحقّق عند الإقلاع كمجموعة واحدة
قاطع انقطع، أو وحدة تغذية فشلت، أو قابس سُحب لا يأتي بـ WM_ENDSESSION ولا PRESHUTDOWN. إن كتبت فوق الملفّ الأصليّ في مكانه، يمكن لمقاطعة في المنتصف أن تترك ملفّاً مختلط المحتويات القديمة والجديدة.
الأساس الكتابة بالكامل إلى ملفّ مؤقّت على المجلّد نفسه، وإفراغه، ثمّ التبديل. يجمع ReplaceFile الخطوات التي تقابل الحفظ إلى ملفّ جديد، وتنحية الأصليّ، وإعادة التسمية، والحذف، ويحمل سمات كزمن الإنشاء وACL وتدفّقات البيانات البديلة. يجب أن يكون الملفّ المستبدَل، وملفّ الاستبدال، والنسخة الاحتياطيّة على المجلّد نفسه. يستدعي File.Replace في .NET هذه الواجهة.12
flowchart TB
accTitle: حفظ ملفّ والاستعادة عند الإقلاع التالي
accDescr: اكتب إلى ملفّ مؤقّت على المجلّد نفسه، افرغ، بدِّل مع الاحتفاظ بنسخة احتياطيّة، ثمّ تحقّق من الملفّ الأوّليّ عند الإقلاع وارجع إلى النسخة الاحتياطيّة إن لزم
tmp["ملفّ مؤقّت على المجلّد نفسه"] --> write["اكتب إلى النهاية وافرغ"]
write --> replace["بدِّل؛ المحتويات القديمة تذهب إلى .bak"]
replace -.-> boot["الإقلاع التالي"]
boot --> valid{"هل الملفّ الأوّليّ سليم؟"}
valid -->|"نعم"| main["اقرأ الملفّ الأوّليّ"]
valid -->|"لا"| backup["ارجع إلى النسخة الاحتياطيّة"]
الشكل 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
flowchart TB
accTitle: الحدّ بين نجاح الكتابة والثبات
accDescr: كتابة عاديّة تبلغ التخزين من ذاكرة نظام التشغيل المؤقّتة بتأخير؛ إفراغ أو كتابة فوريّة يدفعها إلى الالتزام، لكنّ قيود الذاكرة المؤقّتة في جانب الجهاز تبقى
write["ينجح WriteFile"] --> cache["قد يكون في ذاكرة نظام التشغيل المؤقّتة"]
cache --> delayed["كتابة كسولة"]
cache --> flush["افرغ عند نقطة تحقّق"]
delayed --> device["يُطبَّق على التخزين"]
flush --> device
device -.-> limit["حدود الذاكرة المؤقّتة في جانب الجهاز"]
الشكل 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
flowchart TB
accTitle: من كشف UPS إلى إكمال الإغلاق
accDescr: اكشف تحويل UPS إلى البطاريّة عبر مسار الإشعار المناسب، افحص حالة الطاقة، تقدّم إلى الحفظ والإغلاق، وصمِّم التسلسل كلّه ليناسب زمن تشغيل البطاريّة
outage["انقطاع؛ يحوّل UPS إلى البطاريّة"] --> notice["إشعار طاقة على المسار المطابق"]
notice --> check["افحص حالة الطاقة والشحن المتبقّي"]
check --> save["علِّق القياس واحفظ"]
save --> req["اطلب إغلاقاً من نظام التشغيل"]
req --> done["أكمل التنظيف وخروج نظام التشغيل"]
done -.-> time["ناسب التسلسل كلّه ضمن زمن التشغيل"]
الشكل 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
flowchart TB
accTitle: توسيع التحقّق من الإغلاق على مراحل
accDescr: جرِّب إشعارات الواجهة وكلّ عمليّة خروج في بيئة الاختبار، أكِّد الاستعادة بعد توقّف مفاجئ على آلة افتراضيّة، ثمّ تحقّق من صمود فقدان الطاقة بما في ذلك التخزين على عتاد مكافئ للإنتاج
prep["جهِّز جهاز الاختبار والبيانات"] --> notify["جرِّب مسارات الإشعار وعمليّات الخروج"]
notify --> time["قِس زمن التنظيف"]
time --> vm["أكِّد الاستعادة بعد توقّف آلة افتراضيّة مفاجئ"]
vm --> real["تحقّق على عتاد حقيقيّ بما في ذلك التخزين"]
real --> check["أكِّد البيانات والاستعادة عند الإقلاع التالي"]
الشكل 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
flowchart TB
accTitle: اختيار ما تحقّقه من سجلّ أحداث الإغلاق
accDescr: أكِّد العمليّة المبادر والسبب من 1074، وعامل 41 و6008 كأدلّة على إنهاء غير متوقَّع لتقابلها بمعلومات محيطة كرمز فحص الخطأ والتفريغات
log["سجلّ أحداث النظام"] --> normal["1074: العمليّة المبادر والسبب"]
normal --> cleanup["حقِّق معالجة الإشعار ومسار الحفظ"]
log --> unexpected["41 و6008: إنهاء غير متوقَّع"]
unexpected --> evidence["افحص BugcheckCode والتفريغات"]
evidence --> classify["اعزل الانهيار وفقدان الطاقة وما شابه"]
الشكل 19: 41 و6008 ليسا دليلاً على فقدان طاقة نفسه؛ إنّهما مدخل إلى تحقيق إضافيّ.
BugcheckCode غير صفريّ في الحدث 41 دليل على انهيار. إن كان 0 ولا تفريغ ذاكرة، يُشتبَه بفقدان طاقة، لكن قرِّر بمقابلته بالمعلومات المحيطة. إن تبيّن فقدان طاقة، ركِّز على تصميم الحفظ وUPS في القسم 8؛ إن كان خروجاً مخطَّطاً، ركِّز على الإشعارات والتنظيف في الأقسام 3 إلى 6.
10. الخلاصة ── صمِّم حتّى الإقلاع التالي، لا معالجة الخروج فحسب
معالجة الإغلاق ليست عن معالج حدث يعمل مرّة عند الخروج. ما يهمّ جعل الحفظ الروتينيّ ← تنظيفاً قصيراً ← التحقّق والاستعادة عند الإقلاع التالي كلاً واحداً مستمرّاً.
| أين تراجع | نقطة التصميم |
|---|---|
| المعالجة الروتينيّة | احفظ كثيراً وقلِّل الفرق المتبقّي عند الخروج |
| إشعارات خروج الواجهة | أجب على الاستعلام بـ TRUE فوراً، كقاعدة. افصل الحفظ المتكافئ عن التنظيف بعد الالتزام |
| التطبيقات الطرفيّة والخدمات | استخدم الإشعار الذي يناسب نموذج التطبيق. لا تعتمد فقط على ProcessExit في .NET أو على تمديد المهلة |
| عمل لا يمكن مقاطعته | اجمع تسجيل السبب والرفض فقط طالما لزم. استعد لإغلاق قسريّ أيضاً |
| الحفظ والتحميل | إلى جانب الملفّ المؤقّت والإفراغ والتبديل، وفِّر نسخة احتياطيّة وتحقّقاً عند الإقلاع |
| بعد إعادة التشغيل | افحص تسجيل إعادة التشغيل، وبيانات الاستعادة، ومسار تسجيل الدخول أو بدء الخدمة |
في إغلاق مع بدء تشغيل سريع مفعّل، قد تعود النواة والتعريفات من الإسبات. عند عزل مشكلة، حدِّد «إعادة التشغيل» صراحة، واجعل التطبيق يعمل تحت إغلاق كامل وهجين كليهما.5
flowchart TB
accTitle: الفحص النهائيّ من معالجة الخروج إلى الاستعادة
accDescr: أبقِ حالة محفوظة أثناء المعالجة العاديّة، نفِّذ تنظيفاً صغيراً عند إشعار خروج، وتحقّق من البيانات التي تبقى حتّى بلا إشعار للاستعادة عند الإقلاع التالي
daily["أبقِ حالة محفوظة روتينيّاً"] --> endq{"هل ثمّة إشعار خروج؟"}
endq -->|"نعم"| short["اخرج بتنظيف قصير"]
endq -->|"لا"| prior["آخر حالة محفوظة هي كلّ ما ثمّة"]
short --> nextboot["تحقّق واستعد عند الإقلاع التالي"]
prior --> nextboot
nextboot --> restart["عُد إلى العمل تحت الشروط المطلوبة"]
الشكل 20: لا تفصل تنفيذ حدث الخروج عن الحفظ الروتينيّ والاستعادة عند الإقلاع التالي.
أخيراً، جرِّب مسارات الإشعار والزمن المطلوب على جهاز اختبار، وأكِّد الاستعادة بعد توقّف مفاجئ أيضاً. في التحقيق بعد الواقعة، استخدم 1074 / 41 / 6008 أدلّة لعزل خروج مخطَّط عن إنهاء غير متوقَّع.
في المرّة التالية التي تضيف فيها ميزة، اسأل «إن وصل إشعار خروج أثناء هذه المعالجة، أو سُحبت الطاقة، ماذا سيبقى عند الإقلاع التالي؟» تضمين ذلك الجواب في التصميم هو ما يحميك من اكتشاف فساد بيانات في صباح اليوم التالي فقط.
مقالات ذات صلة
- كيفيّة استبدال EXE أو DLL قيد الاستخدام ── Restart Manager ومشكلة «الملفّ قيد الاستخدام» في التحديثات التلقائيّة
- كيفيّة إنشاء خدمات Windows وتشغيلها ── من التمييز عن جدولة المهام إلى تحويل BackgroundService إلى خدمة
- السكون والإسبات وModern Standby والتطبيقات طويلة التشغيل ── منع «التوقّف في منتصف الليل» بالتصميم
- أعماق إدخال/إخراج Windows (الجزء 4) ── مدير الذاكرة المؤقّتة: متى يصل WriteFile فعلاً إلى القرص؟
- تصميم يُبقي السجلات والـ dump عند انهيار تطبيق Windows
- قائمة تحقّق للتعامل الآمن مع العمليات الابن في تطبيقات Windows
مجالات الاستشارة ذات الصلة
تتعامل شركة كومورا سوفت ذ.م.م. مع تصميم وتنفيذ إجراءات مضادّة للإغلاق وفقدان الطاقة لحواسيب الأجهزة والتطبيقات طويلة التشغيل، وتحقيق السبب الجذريّ لفساد بيانات وإخفاقات «كان متوقّفاً في الصباح» تبدأ من إعادة تشغيل Windows Update أو خروج، ومراجعات تصميم معالجة إيقاف خدمات Windows والاستعادة التلقائيّة. مرحَّب بك أن تبدأ من مرحلة «يبدو أنّ شيئاً ينكسر كلّ مرّة نغلق، لكنّني لا أعلم من أين أبدأ».
روابط مرجعيّة
-
Microsoft Learn, WM_QUERYENDSESSION message. حول إرسال WM_QUERYENDSESSION عندما تنتهي جلسة وإعادة التطبيقات TRUE لاحترام قصد المستخدم (DefWindowProc أيضاً يفترض TRUE)؛ وتأجيل التنظيف حتّى WM_ENDSESSION؛ وعرض النظام، بعد 5 ثوانٍ، واجهة تسرد التطبيقات التي تمنع الإغلاق كي يستطيع المستخدم الفرض؛ ومعنى بتّات ENDSESSION_LOGOFF/CLOSEAPP/CRITICAL في lParam؛ وعدم إمكان تمييز الإغلاق وإعادة التشغيل؛ والحفظ المتكرّر لتقليل الكمّ المحفوظ عند الخروج. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
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
-
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
-
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
-
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
-
Microsoft Learn, Shutdown Changes for Windows Vista. حول إمكان تأجيل الردود على WM_QUERYENDSESSION/WM_ENDSESSION بـ 5 ثوانٍ لكلّ منهما، بعدها يختار المستخدم المتابعة أو الإلغاء؛ وعدم قدرة التطبيقات الطرفيّة والتطبيقات بلا نافذة مرئيّة على إلغاء الإغلاق وإنهائها تلقائيّاً بعد 5 ثوانٍ بلا ردّ أو عند ردّ FALSE؛ وتسجيل سبب بـ ShutdownBlockReasonCreate عندما يلزم الحظر؛ وعدم اعتماد التطبيقات على القدرة على حظر الإغلاق. ↩ ↩2 ↩3
-
Microsoft Learn, ShutdownBlockReasonCreate function (winuser.h). حول استدعائها عند بدء عمل لا يمكن مقاطعته لتسجيل سلسلة سبب واستدعاء ShutdownBlockReasonDestroy عند الاكتمال؛ وكونها قابلة للاستدعاء فقط من المؤشّر الذي أنشأ النافذة؛ وإبقاء السلسلة قصيرة وواضحة لأنّ المستخدم يقرأ السبب لثوانٍ قليلة فقط. ↩ ↩2 ↩3
-
Microsoft Learn, SetConsoleCtrlHandler function. حول معاملة عمليّة حمّلت gdi32.dll أو user32.dll كتطبيق Windows لا تُستدعى معالجات CTRL_LOGOFF_EVENT/CTRL_SHUTDOWN_EVENT الخاصّة به؛ وحلّ إنشاء نافذة مخفيّة ومعالجة WM_QUERYENDSESSION/WM_ENDSESSION؛ وإمكان عدم عمل دوالّ الطرفيّة بشكل عاديّ أثناء معالجة الإشارة. ↩
-
Microsoft Learn, SERVICE_PRESHUTDOWN_INFO structure (winsvc.h). حول انتظار SCM بعد إشعار PRESHUTDOWN حتّى تتوقّف الخدمة أو تنتهي المهلة؛ وكون المهلة الافتراضيّة 10 ثوانٍ من Windows 10 Creators Update (بناء 15063) فصاعداً و3 دقائق قبل ذلك؛ وضبطها بـ ChangeServiceConfig2؛ واستمرار تحديثات الحالة أثناء SERVICE_STOP_PENDING. ↩ ↩2
-
Microsoft Learn, RegisterApplicationRestart function (winbase.h). حول التسجيل لإعادة التشغيل في سيناريوهات الانهيار وعدم الاستجابة والتحديث وإعادة تشغيل الحاسوب المدفوعة بتحديث؛ وتحديد وسائط سطر أوامر لإعادة التشغيل؛ والتسجيل قبل حدوث مشكلة، مع كون معالجة WM_QUERYENDSESSION الفرصة الأخيرة في سيناريو التحديث؛ وعدم إعادة تشغيل عمليّات عملت أقلّ من 60 ثانية؛ وتطلّب إعادة التشغيل بعد انهيار أو تعليق موافقة المستخدم؛ وتطلّب الامتداد عبر إعادة تشغيل نظام تشغيل إغلاقاً بـ EWX_RESTARTAPPS/SHUTDOWN_RESTARTAPPS. ↩ ↩2
-
Microsoft Learn, Winlogon automatic restart sign-on (ARSO). حول حفظ Windows Update بيانات اعتماد آخر مستخدم تفاعليّ وضبط Autologon عندما يبدأ إعادة تشغيل تلقائيّة؛ وتسجيل دخول المستخدم تلقائيّاً بعد إعادة التشغيل وقفل الجلسة؛ وحذف بيانات الاعتماد المحفوظة بعد تسجيل دخول ناجح؛ والضبط عبر نهج المجموعة (DisableAutomaticRestartSignOn وغيرها). ↩ ↩2
-
Microsoft Learn, ReplaceFileW function (winbase.h). حول جمع ReplaceFile في دالّة واحدة الخطوات المتعدّدة التي تقابل الحفظ إلى ملفّ جديد، وإعادة تسمية الأصليّ مؤقّتاً، وإعادة تسمية الملفّ الجديد، وحذف الأصليّ؛ وحفظه سمات الملفّ الأصليّ كزمن الإنشاء وDACL والتشفير والضغط والتدفّقات المسمّاة؛ ووجوب كون النسخة الاحتياطيّة والملفّ المستبدَل وملفّ الاستبدال على المجلّد نفسه. ↩ ↩2
-
Microsoft Learn, File Caching. حول ذهاب الكتابات إلى ذاكرة النظام المؤقّتة افتراضيّاً وتطبيقها على القرص بكتابة كسولة؛ وكتابة FILE_FLAG_WRITE_THROUGH البيانات إلى القرص فوراً؛ وإفراغ FlushFileBuffers صراحة؛ وتخزين بيانات تعريف نظام الملفّات دائماً مؤقّتاً، لذا يتطلّب التزام البيانات الوصفيّة إفراغاً أو كتابة فوريّة. ↩ ↩2
-
Microsoft Learn, PBT_APMPOWERSTATUSCHANGE event. حول تسليم هذا الحدث عبر WM_POWERBROADCAST عند التحويل بين البطاريّة وطاقة التيّار المتردّد أو عند انخفاض الشحن المتبقّي؛ واستدعاء GetSystemPowerStatus عند الاستقبال لفحص ACLineStatus وBatteryFlag وBatteryLifePercent وأعضاء SYSTEM_POWER_STATUS الأخرى. ↩ ↩2
-
Microsoft Learn, Troubleshoot unexpected reboots using system event logs. حول تسجيل إعادة تشغيل عاديّة لمعرّف الحدث 1074 (أيّ عمليّة بدأت الإغلاق، نيابة عمّن، ولأيّ سبب)؛ وتسجيل إعادة تشغيل غير متوقَّعة لمعرّف الحدث 41 (Kernel-Power) و6008 (كان الإغلاق السابق غير متوقَّع)؛ واستخدام هذه المعرّفات لعزل نوع إعادة التشغيل. ↩ ↩2
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
ما الذي يفعله بدء التشغيل السريع فعليّاً ── لماذا «إيقاف التشغيل» في Windows ليس إعادة التشغيل
إيقاف التشغيل في Windows إغلاق هجين افتراضيّاً، فيُحفَظ النواة وبرامج التشغيل في hiberfil.sys. لماذا لا تُعاد تهيئتها إلا بإعادة التشغيل،...
Time Travel Debugging ── تسجيل الأخطاء التي لا تتكرّر في التطبيقات طويلة التشغيل وإرجاعها
خطأ يظهر مرّة في الشهر لا يترك في تفريغ الانهيار سوى نتيجته. سجّل التنفيذ وأرجعه بـ WinDbg Time Travel Debugging (TTD): TTD.exe والمخزن ا...
لماذا تتعطّل الوسائط ── قواعد وسائط سطر أوامر Windows
في Windows لا توجد مصفوفة وسائط؛ يصل إلى CreateProcess سلسلة واحدة ويتولّى الطرف المستقبل تقسيمها. نشرح قواعد CommandLineToArgvW وCRT و.N...
انتهاء خدمة برامج تشغيل الطابعات في Windows ── كيف تستعد تطبيقات الأعمال لطباعة التقارير والملصقات
تُنهى Microsoft تدريجيّاً خدمة برامج تشغيل الطابعات v3/v4، ومن تمّوز/يوليو 2026 يُفضَّل برنامج تشغيل فئة IPP. نرتّب في جداول قرار ما يختف...
WinRT هو COM ── IInspectable و.winmd وإسقاط اللغات، ولماذا لا يزال WinUI يركب عقداً ثنائياً
WinRT ليس وقت تشغيل مُداراً؛ بل ABI أُضيفت إليه بيانات وصفية (.winmd) وإسقاطات لغات فوق COM. من علاقة IUnknown وIInspectable إلى تهيئة HW...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
ندعم تطوير برامج ويندوز للأعمال، وتكامل الأجهزة، وأدوات التواصل.
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- مشكلة لم يصلحها «إيقاف التشغيل» اختفت بعد «إعادة التشغيل». لماذا؟
- على أنظمة تشغيل العميل من 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، ويكشف التحويل إلى البطاريّة، وينتقل إلى إغلاق آمن.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.