Windows ชัตดาวน์จากมุมของแอป — รอดจากการแจ้งออก รีสตาร์ต และการตัดไฟอย่างถูกต้อง

· · Windows, ชัตดาวน์, การพัฒนา Windows, บริการ Windows, พีซีอุปกรณ์, ความสมบูรณ์ของข้อมูล, การทำงานระยะยาว, UPS

“หลังรีสตาร์ตข้ามคืนจาก Windows Update แอปวัดบนพีซีอุปกรณ์ล้มกลางเขียน และเช้าไฟล์วัดเสีย” “มีคนเซ็นเอาต์จากพีซีร่วมแล้วบ่นว่าการแก้ไขที่ยังไม่เซฟหาย” — สำหรับแอป Windows ที่รันยาว คำปรึกษาสองอย่างนี้เป็นคลาสสิก

สิ่งที่ทั้งสองไซต์ร่วมกันคือการถือชัตดาวน์เป็น “อีเวนต์ผิดปกติที่ไม่ควรเกิด” ในความเป็นจริง จากรีสตาร์ตอัตโนมัติของ Windows Update การเซ็นเอาต์ของผู้ใช้ และชัตดาวน์ที่ UPS เริ่ม จนถึงการตัดไฟโดยไม่ประกาศ อีเวนต์ที่ตัดการรันจากนอกแอปจะมา ไม่ช้าก็เร็ว คุณกันไม่ให้มันมาไม่ได้ สิ่งที่กันได้คือ “เสียข้อมูลเมื่อมันมา”

โชคดีที่ Windows มีกลไกที่แจ้งแอปก่อนชัตดาวน์ ทั้งแอป GUI แอปคอนโซล และบริการ มุ่งไปที่เจ้าหน้าที่ไอทีในธุรกิจขนาดกลางและขนาดย่อม และนักพัฒนาแอป Windows (โดยเฉพาะแอปพีซีอุปกรณ์และรันยาว) บทความนี้จัดระเบียบวิธีรับการแจ้งเหล่านั้น วิธีออกแบบคลีนอัปที่ “จบในไม่กี่วินาที” การกู้คืนอัตโนมัติหลังรีสตาร์ต และการเตรียมสำหรับการตัดไฟที่ไม่มีการแจ้ง — ทั้งหมดยึดแหล่งปฐมภูมิ Microsoft Learn ณ สิงหาคม 2026

1. สรุปก่อนเลย

  • ออกแบบชัตดาวน์เป็น “อีเวนต์ปกติที่จะมา ไม่ช้าก็เร็ว” เวลาที่ใช้ได้หลังรับการแจ้งโดยหลักแล้วประมาณ 5 วินาทีเท่านั้น ดังนั้นการออกแบบที่รีบเซฟทุกอย่างในที่นั้นจะพัง ข้อกำหนดเกิดคือออโตเซฟบ่อยเพื่อให้ “เดลต้าที่ต้องเซฟตอนชัตดาวน์” เล็ก1
  • บนไคลเอนต์ OS จาก Windows 8 เป็นต้นไป เมื่อ fast startup เปิด (ค่าเริ่มต้นบนพีซีส่วนใหญ่ที่รองรับไฮเบอร์เนต) “Shut down” คือ hybrid shutdown และเคอร์เนลเพียงไฮเบอร์เนต สิ่งที่ถูกรีเซ็ตเต็มมีเพียง “Restart” นั่นคือเหตุผลจริงของ “ฉันชัตดาวน์แล้วยังไม่ดีขึ้น แล้วรีสตาร์ตแล้วดี”2
  • แอป GUI ควรคืน TRUE ทันทีให้ WM_QUERYENDSESSION และทำคลีนอัปใน WM_ENDSESSION โดยหลักห้ามคืน FALSE (ปฏิเสธ)1
  • เฉพาะเมื่อมีปฏิบัติการที่ขัดจังหวะไม่ได้จริง ๆ ควรแสดงเหตุผลด้วย ShutdownBlockReasonCreate แม้กระนั้นผู้ใช้และ OS บังคับทำต่อได้ ดังนั้นการออกแบบที่สมมติว่า “เราบล็อกได้” ไม่ตั้งอยู่ได้34
  • แอปคอนโซลรับการแจ้งด้วย SetConsoleCtrlHandler ระยะผ่อนสั้นกว่าอีก — ค่าเริ่มต้น 5 วินาทีสำหรับการปิดคอนโซล มีกับดักด้วย: ในโปรเซสที่โหลด gdi32.dll หรือ user32.dll อีเวนต์บางอย่างเหล่านี้ไม่มาถึง56
  • คลีนอัปที่พึ่ง AppDomain.ProcessExit ของ .NET ไม่รัน จาก .NET 10 เป็นต้นไป บนเส้นทางที่โปรเซสถูก “ยุติจากภายนอก” ในการออกปกติ เช่น กลับจาก Main มันยังรันเหมือนเดิม แต่เพราะรันไทม์ไม่ให้การจัดการเริ่มต้นสำหรับสัญญาณยุติ เช่น ปิดคอนโซลและชัตดาวน์ คลีนอัปบนเส้นทางเหล่านั้นต้องย้ายไปการแจ้งที่ตรงกับโมเดลแอป7
  • บริการ Windows รับ SERVICE_ACCEPT_PRESHUTDOWN ได้เร็วกว่า และด้วยระยะผ่อนที่ตั้งค่าได้ กว่า SERVICE_ACCEPT_SHUTDOWN (ระยะผ่อนประมาณ 20 วินาที) อย่างไรก็ตาม ค่าหมดเวลา PRESHUTDOWN เริ่มต้นถูกย่อเหลือ 10 วินาทีจาก Windows 10 Creators Update เป็นต้นไป ดังนั้นไม่ว่าทางใดคุณต้องการการออกแบบที่ไม่เอนระยะผ่อนมากเกินไป89
  • การกู้คืนอัตโนมัติหลังรีสตาร์ตทำได้ด้วยการรวม RegisterApplicationRestart กับ ARSO (เซ็นอินอัตโนมัติ) มีเส้นทางกู้สำหรับแครช ไม่ตอบสนอง และรีสตาร์ตจากอัปเดต1011
  • การตัดไฟไม่มีการแจ้งเลย รูปแบบมาตรฐานคือเขียนให้ครบลงไฟล์ชั่วคราว ฟลัช และสลับด้วย ReplaceFile แต่เพราะ ReplaceFile ก็ไม่รับประกันความเป็นอะตอมข้ามการตัดไฟ เส้นทางกู้ของสำรอง (.bak) บวกการตรวจตอนโหลดเป็นชุด หลังเหตุการณ์แยกได้จากล็อกอีเวนต์ (1074/41/6008)121314

ในหนึ่งประโยค ข้อสรุปของบทความนี้คือ: “รักษารัฐที่ปิดร้านในไม่กี่วินาทีได้เสมอเมื่อการแจ้งมาถึง และเขียนในทางที่ไม่พังแม้ในการตัดไฟที่ไม่มีการแจ้ง”

2. สิ่งที่เกิดตอนชัตดาวน์ — สี่ทางจบ

2.1. เซ็นเอาต์ ชัตดาวน์ รีสตาร์ต และการตัดไฟ

จากมุมแอป สิ่งสำคัญคือสองแกน: “เซสชันผู้ใช้จบอย่างไร” และ “เกิดอะไรกับเคอร์เนล”

การดำเนินการ เซสชันผู้ใช้ เคอร์เนลและไดรเวอร์ การแจ้งไปยังแอป
เซ็นเอาต์ จบ รันต่อ WM_QUERYENDSESSION (ENDSESSION_LOGOFF) → WM_ENDSESSION
ชัตดาวน์ (fast startup เปิด) จบ ไฮเบอร์เนต (บันทึกลง hiberfil.sys) WM_QUERYENDSESSION → WM_ENDSESSION, (PRE)SHUTDOWN ไปยังบริการ
รีสตาร์ต จบ จบสมบูรณ์ บูตถัดไปคือบูตเต็ม เหมือนด้านบน
ตัดไฟ หายทันที หายทันที ไม่มี

เซ็นเอาต์และชัตดาวน์จากมุมแอปเกือบเป็นอีเวนต์เดียวกัน หากบิต ENDSESSION_LOGOFF ถูกตั้งใน lParam ของ WM_QUERYENDSESSION คือเซ็นเอาต์ หากเป็น 0 คือชัตดาวน์หรือรีสตาร์ต (แยกสองอย่างไม่ได้)1 กล่าวคือ ความประมาทที่ “แค่เซ็นเอาต์ ไม่เป็นไร” ไม่ตั้งอยู่ และการออกแบบที่ถูกคือเรียกโค้ดคลีนอัปเดียวกัน

สี่ทางจบ และการแจ้งไปยังแอปเซ็นเอาต์ ชัตดาวน์ และรีสตาร์ตส่งการแจ้ง WM_QUERYENDSESSION ถึง WM_ENDSESSION และคลีนอัปจบในไม่กี่วินาที มีเพียงการตัดไฟที่ไม่มีการแจ้งเลย จึงเตรียมด้วยการออกแบบการเขียนในบทที่ 8 และ UPSเซ็นเอาต์QUERY → ENDSESSIONชัตดาวน์รีสตาร์ตตัดไฟไม่แจ้ง: เขียน + UPSคลีนอัปในวินาที

ภาพ 1: เซ็นเอาต์ ชัตดาวน์ และรีสตาร์ตส่งการแจ้ง WM_QUERYENDSESSION ถึง WM_ENDSESSION และคลีนอัปจบในไม่กี่วินาที มีเพียงการตัดไฟที่ไม่มีการแจ้งเลย จึงเตรียมด้วยการออกแบบการเขียนในบทที่ 8 และ UPS

2.2. เหตุผลจริงของ “ฉันชัตดาวน์แล้วยังไม่ดีขึ้น” — hybrid shutdown

แถวที่พลาดง่ายคือแถวที่สองในตาราง บนไคลเอนต์ OS จาก Windows 8 เป็นต้นไป fast startup (hybrid shutdown) เปิดโดยค่าเริ่มต้นบนพีซีที่รองรับไฮเบอร์เนต และพฤติกรรมของ “Shut down” เปลี่ยน เซ็นเอาต์ของเซสชันผู้ใช้ยังเกิดตามปกติ แต่ เซสชันเคอร์เนลไม่ถูกปิด มันถูกบันทึก ทั้งไดรเวอร์อุปกรณ์ ลงไฟล์ไฮเบอร์เนต (hiberfil.sys) และคืนตามที่เป็นในการบูตครั้งถัดไป นั่นทำให้สตาร์ตเร็วขึ้น แต่สถานะเคอร์เนลและไดรเวอร์รอดแม้คุณตัดไฟ2 อย่างไรก็ตาม นี่เป็น พฤติกรรมมีเงื่อนไข ในสภาพแวดล้อมที่ไฮเบอร์เนตเองถูกปิด (powercfg /hibernate off) ที่นโยบายหรือ Power Options ปิด fast startup และบน Windows Server ชัตดาวน์คือชัตดาวน์เต็มแบบดั้งเดิม คุณบอกได้ว่าพีซีนั้นรันทางใดจากช่อง “Turn on fast startup” ใน Power Options หรือจากว่า powercfg /a (สถานะสลีปที่ใช้ได้) แสดง “Fast Startup”

เกิดอะไรกับเคอร์เนลในการดำเนินการ Shut downการดำเนินการ Shut down แยกเป็นชัตดาวน์เต็มหรือไฮเบอร์เนตเคอร์เนลตามว่า fast startup เปิดหรือไม่ และ Restart ทำบูตเต็มเสมอFast startup เปิดไฮเบอร์เนตปิด / ServerShut downRestartเซสชันจบ + ไฮเบอร์เนตเคอร์เนลชัตดาวน์เต็มถัดไป: คืนเคอร์เนลถัดไป: บูตเต็ม

ภาพ 2: การดำเนินการ Shut down แยกเป็นชัตดาวน์เต็มหรือไฮเบอร์เนตเคอร์เนลตามว่า fast startup เปิดหรือไม่ และ Restart ทำบูตเต็มเสมอ

“Restart” ในทางกลับกันรันวงจรบูตสมบูรณ์เสมอ หลังอัปเดตไดรเวอร์ เช่น คุณต้องการสถานะใหม่สมบูรณ์2 จากนั้นปรากฏการณ์หลายอย่างที่ได้ยินในสนามลงที่

  • “ฉันชัตดาวน์แล้วเปิดใหม่ แต่ปัญหาอุปกรณ์ไม่หาย” — เคอร์เนลและไดรเวอร์เพียงถูกคืนจากไฮเบอร์เนต ไม่ได้ถูกรีเซ็ต
  • “ดีขึ้นหลังรีสตาร์ต” — เพราะบูตเต็มเริ่มต้นพวกมัน
  • ขั้นตอนเหตุการณ์พีซีอุปกรณ์ควรกล่าว “Restart” ไม่ใช่ “ปิดแล้วเปิด”

หากต้องการทำให้ชัตดาวน์เต็มชัดจากบรรทัดคำสั่ง shutdown /s (ค่าเริ่มต้นของ Shutdown.exe คือชัตดาวน์เต็ม) หากต้องการพฤติกรรมไฮบริดเริ่มต้น shutdown /s /hybrid2 การปิด fast startup ไม่แนะนำ ฝั่งแอปควรถือว่า “ตอนชัตดาวน์เคอร์เนลอาจเพียงไฮเบอร์เนต” — เช่น อย่าประมาณ “เวลาทำงานสะสม” จากเวลาบูตของ OS — และออกแบบไม่ให้พังไม่ว่าทางใด (fast startup เปิดหรือปิดต่างตามสภาพแวดล้อม)

3. แอป GUI ควรประพฤติอย่างไร — WM_QUERYENDSESSION และ WM_ENDSESSION

3.1. สองข้อความแยกงานอย่างไร

แอปที่มีหน้าต่างและคิวข้อความถูกแจ้งการจบเซสชันสองขั้น1

  1. WM_QUERYENDSESSION — คำถาม: “จบได้หรือไม่” แอปควร คืน TRUE ทันที การตอบเริ่มต้นของ DefWindowProc ก็เป็น TRUE เช่นกัน อย่าเริ่มคลีนอัปที่นี่
  2. WM_ENDSESSION (wParam=TRUE) — การแจ้งที่ยืนยันแล้ว: “เซสชันกำลังจบจริง” คลีนอัปเกิดที่นี่

การคืน FALSE ให้ WM_QUERYENDSESSION ยกเลิกชัตดาวน์ได้ แต่เอกสารชัดว่า “คุณควรคืน TRUE และเคารพเจตนาของผู้ใช้” และแอปที่คืน FALSE ยังถูกเปิดใน UI เต็มจอเป็น “แอปที่กำลังป้องกันชัตดาวน์” แอปคอนโซลและแอปที่ไม่มีหน้าต่างมองเห็นยกเลิกชัตดาวน์ไม่ได้ตั้งแต่แรก และหากไม่ตอบภายใน 5 วินาทีถูกยุติอัตโนมัติ14

ไหลของการแจ้งจบเซสชันสองขั้นการคืน TRUE ให้คำถาม WM_QUERYENDSESSION ยืนยันด้วย WM_ENDSESSION และคลีนอัปรัน การปฏิเสธด้วย FALSE แสดงแอปเป็นตัวป้องกันชัตดาวน์ และการไม่ตอบประมาณ 5 วินาทีบังคับทำต่อได้TRUE(กฎ)FALSE(ปฏิเสธ)ไม่ตอบประมาณ 5 วินาทีบังคับทำต่อยกเลิกWM_QUERYENDSESSIONWM_ENDSESSION(ยืนยันแล้ว)แสดงว่าบล็อกชัตดาวน์ถือว่าแฮงคลีนอัปที่นี่โปรเซสออกชัตดาวน์ถูกยกเลิก

ภาพ 3: การคืน TRUE ให้คำถาม WM_QUERYENDSESSION ยืนยันด้วย WM_ENDSESSION และคลีนอัปรัน การปฏิเสธด้วย FALSE แสดงแอปเป็นตัวป้องกันชัตดาวน์ และการไม่ตอบประมาณ 5 วินาทีบังคับทำต่อได้

3.2. หากไม่ตอบจะเกิดอะไร — กำแพง 5 วินาที

ทั้งบน WM_QUERYENDSESSION และ WM_ENDSESSION คุณเลื่อนการตอบได้ ประมาณ 5 วินาที เกินนั้น ระบบแสดงหน้าจอ “แอปนี้กำลังป้องกันชัตดาวน์” และผู้ใช้เลือกบังคับทำต่อได้ (= บังคับยุติแอป)4 โปรเซสที่ถูกบังคับยุติไม่ได้รับโอกาสอีกที่จะเซฟให้เสร็จ

จุดออกแบบจึงเป็นสองอย่างนี้

  • เก็บคลีนอัปให้ปริมาณที่จบใน 5 วินาที Microsoft เองแนะนำให้เซฟข้อมูลบ่อยในการทำงานปกติเพื่อให้ต้องเซฟตอนชัตดาวน์น้อยลง และเซฟข้อมูลที่ยังไม่เซฟไปตำแหน่งชั่วคราวเพื่อคืนในการเปิดครั้งถัดไป1
  • อย่าวางกล่องยืนยันระหว่างชัตดาวน์ ขณะที่คุณนั่งรอ “ต้องการเซฟหรือไม่” 5 วินาทีผ่าน ตกไปฝั่งปลอดภัยเงียบ ๆ (ออโตเซฟ)

3.3. การอิมพลีเมนต์ใน WinForms และ WPF

ในแอปเดสก์ท็อป .NET ข้อความเหล่านี้ถูกแปลเป็นอีเวนต์เฟรมเวิร์ก ใน WinForms FormClosing ถูกยก และ CloseReason บอกว่าชัตดาวน์เป็นสาเหตุหรือไม่

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

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

ใน WPF อีเวนต์ Application.SessionEnding (แอตทริบิวต์ XAML SessionEnding หรือการโอเวอร์ไรด์ OnSessionEnding) สอดคล้อง

อีเวนต์ WinForms/WPF แมปกับข้อความอย่างไรระยะสอบถามของ WM_QUERYENDSESSION แมปกับ WinForms FormClosing และ WPF SessionEnding และสิ่งที่ทำที่นั่นคือเซฟสแนปช็อตแบบ idempotent อย่างมาก มีอีเวนต์สอดคล้องสำหรับ WM_ENDSESSION ที่ยืนยันแล้วไม่มี จึงรับใน WndProc หรือฮุกและทำคลีนอัปที่รันได้เฉพาะหลังยืนยันWM_QUERYENDSESSIONWinForms: FormClosingWPF: SessionEndingสแนปช็อต idempotent เท่านั้นWM_ENDSESSIONไม่มีอีเวนต์: ฮุก WndProcคลีนอัปหลังยืนยัน

ภาพ 4: ระยะสอบถามของ WM_QUERYENDSESSION แมปกับ WinForms FormClosing และ WPF SessionEnding และสิ่งที่ทำที่นั่นคือเซฟสแนปช็อตแบบ idempotent อย่างมาก มีอีเวนต์สอดคล้องสำหรับ WM_ENDSESSION ที่ยืนยันแล้วไม่มี จึงรับใน WndProc หรือฮุกและทำคลีนอัปที่รันได้เฉพาะหลังยืนยัน

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

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

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

มีข้อควรระวังหนึ่งที่นี่ ทั้ง FormClosing (CloseReason.WindowsShutDown) และ SessionEnding ของ WPF สอดคล้องกับ ระยะสอบถาม (WM_QUERYENDSESSION) หากแอปอื่นปฏิเสธ ชัตดาวน์ถูกยกเลิกและแอปของคุณรันต่อ ดังนั้นสิ่งที่ทำในอีเวนต์เหล่านี้ได้คือ เซฟสแนปช็อตแบบ idempotent ที่ไม่เป็นอันตรายหากชัตดาวน์ถูกยกเลิก และให้ผลเดียวกันไม่ว่ารันกี่ครั้ง หากต้องการ “คลีนอัปที่ต้องทำเฉพาะเมื่อกำลังจบจริง” (ตัดการเชื่อมต่อ คืนทรัพยากร เป็นต้น) ฮุก WM_ENDSESSION ที่ยืนยันแล้ว (wParam=TRUE) โดยตรงใน WndProc และทำที่นั่น

บนเส้นทางใด พับตัวเข้าฟังก์ชัน “เซฟสแนปช็อต” ร่วม และ เขียนข้อมูลคืนสำหรับการออกปกติ ชัตดาวน์ และ (ถ้าได้) แครชในรูปแบบเดียวกัน เพื่อให้ตรรกะคืนในการเปิดครั้งถัดไปเป็นเส้นทางเดียว การออกแบบให้เหลือข้อมูลแม้ตอนแครชอยู่ใน “ออกแบบแอป Windows ให้เหลือล็อกและดัมป์เมื่อแครช

4. หากต้องบล็อกจริง ๆ — ShutdownBlockReasonCreate

ปฏิบัติการที่พังทางกายภาพหากถูกตัดกลางทาง เช่น เขียนซีดีหรือเฟิร์มแวร์ เป็นข้อยกเว้น แนวปฏิบัติที่ถูกที่นี่คือ ลงทะเบียนสตริงเหตุผลด้วย ShutdownBlockReasonCreate เมื่อปฏิบัติการที่ขัดจังหวะไม่ได้เริ่ม และเรียก ShutdownBlockReasonDestroy ทันทีเมื่อเสร็จ เมื่อมีการขอชัตดาวน์ เหตุผลนั้นถูกแสดงบนหน้าจอ “แอปนี้กำลังป้องกันชัตดาวน์” และผู้ใช้ตัดสินได้ว่าจะทำต่อหรือยกเลิก3

ไหลของการป้องกันด้วย ShutdownBlockReasonCreateลงทะเบียนเหตุผลเมื่อปฏิบัติการที่ขัดจังหวะไม่ได้เริ่ม หากมีคำขอชัตดาวน์ขณะป้องกัน เหตุผลถูกแสดงเต็มจอและ WM_QUERYENDSESSION ถูกปฏิเสธด้วย FALSE ผู้ใช้ยกเลิกหรือบังคับทำต่อได้ และเหตุผลถูกล้างเมื่อปฏิบัติการเสร็จยกเลิกบังคับทำต่อเริ่มงานที่ขัดจังหวะไม่ได้ShutdownBlockReasonCreateรันบนเธรดเวิร์กเกอร์เสร็จ: Destroyชัตดาวน์ระหว่างนี้แสดงเหตุผล + FALSEโปรเซสออก

ภาพ 5: ลงทะเบียนเหตุผลเมื่อปฏิบัติการที่ขัดจังหวะไม่ได้เริ่ม หากมีคำขอชัตดาวน์ขณะป้องกัน เหตุผลถูกแสดงเต็มจอและ WM_QUERYENDSESSION ถูกปฏิเสธด้วย FALSE ผู้ใช้ยกเลิกหรือบังคับทำต่อได้ และเหตุผลถูกล้างเมื่อปฏิบัติการเสร็จ

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

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

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

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

ความเข้าใจผิดง่ายที่นี่คือการแยกบทบาท ShutdownBlockReasonCreate ทำเพียงลงทะเบียนสตริงเหตุผล มันไม่ได้หยุดชัตดาวน์เอง สิ่งที่ดึงชัตดาวน์จริงคือการจัดการของคุณเองที่ คืน FALSE ให้ WM_QUERYENDSESSION ขณะตั้งธงป้องกัน ดังข้างบน ใช้สองอย่างเป็นชุด และล้างทั้งคู่ทันทีเมื่อปฏิบัติการเสร็จ นอกจากนี้ รันปฏิบัติการที่ป้องกันเองบนเธรดเวิร์กเกอร์และให้เธรด UI ประมวลผลข้อความได้ — กลไกปฏิเสธทำงานเมื่อข้อความมาถึงเท่านั้น (และแม้กระนั้นผู้ใช้และ OS บังคับทำต่อได้ ดังนั้นการออกแบบการเขียนที่ไม่พัง “หากมันไม่หยุด” — บทที่ 8 — ยังจำเป็น)

มีข้อควรระวังด้านการปฏิบัติสามอย่าง

  • เก็บสตริงเหตุผลสั้นและเฉพาะ ผู้ใช้รีบและจะอ่านเพียงไม่กี่วินาที เอกสารเองให้ “Burning a CD” เป็นตัวอย่างที่เหมาะสม3
  • อย่าปล่อยให้ลงทะเบียนตลอดชีวิตของแอป “เฉพาะขณะปฏิบัติการที่ขัดจังหวะไม่ได้กำลังดำเนิน” คือสิ่งที่ API สมมติ
  • อย่าออกแบบบนสมมติฐานว่าบล็อกได้ ผู้ใช้เลือกบังคับทำต่อได้ และการชัตดาวน์บังคับ (ENDSESSION_CRITICAL) จะไม่รอตั้งแต่แรก เอกสารชัด: “Applications should not depend on being able to block shutdown”4

5. แอปคอนโซลและโปรเซสพื้นหลังควรประพฤติอย่างไร

5.1. SetConsoleCtrlHandler และระยะผ่อนสั้น

แอปคอนโซลรับข้อความหน้าต่างไม่ได้ ดังนั้นสัญญาณควบคุมมาถึงฟังก์ชันแฮนเดิลที่ลงทะเบียนด้วย SetConsoleCtrlHandler ระยะผ่อนเริ่มต้นต่อสัญญาณมีดังนี้5

สัญญาณ เมื่อเกิด ระยะผ่อนเริ่มต้น
CTRL_C_EVENT / CTRL_BREAK_EVENT Ctrl+C / Ctrl+Break ไม่มีหมดเวลา
CTRL_CLOSE_EVENT ปิดคอนโซล “End task” ของ Task Manager (การฆ่าโปรเซสบังคับจากแท็บ “Details” คือออกทันทีโดยไม่แจ้ง และอยู่นอกตารางนี้) ประมาณ 5 วินาที
CTRL_SHUTDOWN_EVENT ชัตดาวน์ระบบ (โปรเซสบริการ) ประมาณ 20 วินาที

มีสองจุดที่ต้องดู ประการแรก โดยพื้นฐานมีเพียงโปรเซสที่รันเป็นบริการรับ CTRL_LOGOFF_EVENT และ CTRL_SHUTDOWN_EVENT ได้ แอปในเซสชันเชิงโต้ตอบถูกยุติตอนเซ็นเอาต์ ดังนั้นการออกแบบที่รอสัญญาณเหล่านี้ไม่ตั้งอยู่5 ประการที่สอง โปรเซสที่โหลด gdi32.dll หรือ user32.dll ถูกถือเป็นแอป Windows แม้คุณคิดว่าเป็นแอปคอนโซล และแฮนเดิล LOGOFF/SHUTDOWN ไม่ถูกเรียก วิธีแก้ทางการคือสร้างหน้าต่างซ่อนและรับ WM_QUERYENDSESSION/WM_ENDSESSION6

ระยะผ่อนต่อสัญญาณคอนโซลCtrl+C และ Ctrl+Break ไม่มีหมดเวลาชัด ปิดคอนโซลมีประมาณ 5 วินาที และสัญญาณชัตดาวน์ไปยังโปรเซสบริการมีประมาณ 20 วินาที เกินนั้นบังคับยุติโปรเซสไม่มีหมดเวลาประมาณ 5 วินาทีประมาณ 20 วินาทีCTRL_C / BREAKคลีนอัป HandlerRoutineCTRL_CLOSECTRL_SHUTDOWNบังคับฆ่าหลังระยะผ่อน

ภาพ 6: Ctrl+C และ Ctrl+Break ไม่มีหมดเวลาชัด ปิดคอนโซลมีประมาณ 5 วินาที และสัญญาณชัตดาวน์ไปยังโปรเซสบริการมีประมาณ 20 วินาที เกินนั้นบังคับยุติโปรเซส

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

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

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

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

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

5.2. กับดักของ .NET — อย่าพึ่ง ProcessExit

ใน .NET มีรูปแบบสต็อกมานานว่า “แค่คลีนอัปใน AppDomain.ProcessExit” แต่ จาก .NET 10 รันไทม์ไม่ให้แฮนเดิลสัญญาณยุติเริ่มต้นอีก และทั้ง ProcessExit และ AssemblyLoadContext.Unloading ไม่ยิงบน CTRL_CLOSE_EVENT/CTRL_SHUTDOWN_EVENT แฮนเดิลเริ่มต้นของ OS เพียงยุติโปรเซสทันที7

ProcessExit เปลี่ยนใน .NET 10 อย่างไรจนถึง .NET 9 แฮนเดิลสัญญาณเริ่มต้นของรันไทม์รับสัญญาณยุติ ยก ProcessExit แล้วออก จาก .NET 10 รันไทม์ไม่ให้แฮนเดิลเริ่มต้น การจัดการเริ่มต้นของ OS ยุติโปรเซสทันที และคุณลงทะเบียนแฮนเดิลเองCTRL_CLOSE / SHUTDOWNจนถึง .NET 9: ProcessExitจาก .NET 10: ออกทันทีลงทะเบียนแฮนเดิลเอง

ภาพ 7: จนถึง .NET 9 แฮนเดิลสัญญาณเริ่มต้นของรันไทม์รับสัญญาณยุติ ยก ProcessExit แล้วออก จาก .NET 10 รันไทม์ไม่ให้แฮนเดิลเริ่มต้น การจัดการเริ่มต้นของ OS ยุติโปรเซสทันที และคุณลงทะเบียนแฮนเดิลเอง

แทนที่จะเป็นเช่นนั้น ย้ายไปเส้นทางมาตรฐานของแต่ละโมเดลแอป

  • แอป GUI: FormClosing / SessionEnding จากบทก่อน
  • Generic Host (รวม Worker Service): IHostApplicationLifetime และ BackgroundService.StopAsync ทำให้ระยะผ่อนหยุดชัดด้วย HostOptions.ShutdownTimeout
  • แอปคอนโซลเปล่า: SetConsoleCtrlHandler (หรือสมัครเทียบเท่า SIGINT/SIGTERM ด้วย PosixSignalRegistration)
แต่ละโมเดลแอปรับการแจ้งออกที่ไหนแอป GUI ใช้ FormClosing และ SessionEnding บวกฮุก WM_ENDSESSION สำหรับงานที่ยืนยันแล้ว Generic Host ใช้ IHostApplicationLifetime และ StopAsync แอปคอนโซลเปล่าใช้ SetConsoleCtrlHandler หรือ PosixSignalRegistration การพึ่ง ProcessExit ไม่ยิงบนเส้นทางสัญญาณภายนอกGUIไม่ใช่ GUIHostคอนโซลโมเดลแอปใดFormClosing / SessionEndingHost หรือคอนโซลฮุก ENDSESSIONLifetime + StopAsyncSetConsoleCtrlHandlerตั้ง ShutdownTimeoutอย่าพึ่ง ProcessExit

ภาพ 8: แอป GUI ใช้ FormClosing และ SessionEnding บวกฮุก WM_ENDSESSION สำหรับงานที่ยืนยันแล้ว Generic Host ใช้ IHostApplicationLifetime และ StopAsync แอปคอนโซลเปล่าใช้ SetConsoleCtrlHandler หรือ PosixSignalRegistration การพึ่ง ProcessExit ไม่ยิงบนเส้นทางสัญญาณภายนอก

ระยะผ่อนต่างตามเส้นทาง — ประมาณ 5 วินาทีสำหรับ GUI และปิดคอนโซล ระยะผ่อน SCM ของบทที่ 6 สำหรับบริการ (ประมาณ 20 วินาที หรือค่าที่ตั้งสำหรับ PRESHUTDOWN) และไม่มีหมดเวลาชัดสำหรับ Ctrl+C บนทุกเส้นทาง อย่างไรก็ตาม ระยะผ่อนมีจำกัดและพึ่งไม่ได้ ดังนั้นแกนออกแบบคือ กรณีปกติคือ “เซฟแล้วที่แต่ละจุดตรวจการประมวลผล” ไม่ใช่ “ทำงานหนักในอีเวนต์ออก”

6. บริการ Windows ควรประพฤติอย่างไร — SHUTDOWN และ PRESHUTDOWN

6.1. การแจ้งชัตดาวน์สองชนิด

บริการไม่ได้รับผลจากการเซ็นเอาต์ แต่ถูกหยุดตอนชัตดาวน์และรีสตาร์ต การแจ้งมาเป็นรหัสควบคุมจาก Service Control Manager (SCM) และการรับต้องการประกาศธงยอมรับ8

การประกาศ การแจ้งที่มาถึง จังหวะและระยะผ่อน
SERVICE_ACCEPT_SHUTDOWN SERVICE_CONTROL_SHUTDOWN ถูกแจ้งระหว่างการประมวลผลชัตดาวน์ ค่าเริ่มต้น ประมาณ 20 วินาที เพดาน WaitToKillServiceTimeout
SERVICE_ACCEPT_PRESHUTDOWN SERVICE_CONTROL_PRESHUTDOWN ถูกแจ้ง ก่อน SHUTDOWN SCM รอจนกว่าบริการจะหยุดหรือหมดเวลา
ลำดับการแจ้งชัตดาวน์ไปยังบริการเมื่อชัตดาวน์เริ่ม บริการที่ประกาศ PRESHUTDOWN ถูกแจ้งก่อนด้วยระยะผ่อนที่ตั้ง จากนั้นการแจ้ง SHUTDOWN ถูกส่งด้วยค่าเริ่มต้นประมาณ 20 วินาที และโปรเซสถูกยุติเมื่อระยะผ่อนหมดชัตดาวน์เริ่มPRESHUTDOWN(หากประกาศ)SHUTDOWN(ประมาณ 20 วินาที)ระยะผ่อนหมด → ออก

ภาพ 9: เมื่อชัตดาวน์เริ่ม บริการที่ประกาศ PRESHUTDOWN ถูกแจ้งก่อนด้วยระยะผ่อนที่ตั้ง จากนั้นการแจ้ง SHUTDOWN ถูกส่งด้วยค่าเริ่มต้นประมาณ 20 วินาที และโปรเซสถูกยุติเมื่อระยะผ่อนหมด

หมดเวลา PRESHUTDOWN ตั้งค่าด้วย ChangeServiceConfig2 (SERVICE_CONFIG_PRESHUTDOWN_INFO) ได้ ค่าเริ่มต้นคือ 10 วินาทีจาก Windows 10 Creators Update (build 15063) เป็นต้นไป และ 3 นาทีก่อนนั้น9 หากยังทำงานจากความรู้เก่าที่ “PRESHUTDOWN ให้ 3 นาที” บน OS ปัจจุบันคุณมีเพียง 1/18 ของระยะผ่อนที่คาด นอกจากนี้ PRESHUTDOWN ดึงชัตดาวน์ทั้งระบบไว้ช่วงนั้น ดังนั้นเอกสารก็กล่าวว่า “ควรใช้เฉพาะในสถานการณ์พิเศษ”8

แนวปฏิบัติฝั่งแฮนเดิลก็สำคัญ แฮนเดิลควบคุมต้องคืนภายใน 30 วินาที ทิ้งงานหยุดที่ใช้เวลานานให้เธรดอื่น รายงาน SERVICE_STOP_PENDING และคืนทันที8

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

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

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

6.2. การออกแบบที่ไม่พึ่งระยะผ่อน

การขยายเพดานระยะผ่อน WaitToKillServiceTimeout ด้วยการเขียนจากฝั่งบริการไม่แนะนำอย่างชัด เอกสารขอตรงกันข้าม — บริการควรจบคลีนอัปให้เร็วที่สุดเพื่อให้เครื่องที่ใช้ UPS ชัตดาวน์เสร็จก่อนแบตเตอรี่หมด คำแนะนำคือเซฟบ่อยในการทำงานปกติเพื่อให้ข้อมูลที่ยังไม่เซฟน้อยที่สุด ไม่ใช้เวลาปล่อยหน่วยความจำตอนชัตดาวน์ และไม่รอคำตอบนานเกินไปเมื่อแจ้งคู่เครือข่าย นอกจากนี้ SCM ตอนชัตดาวน์โดยค่าเริ่มต้นไม่พิจารณาการพึ่งพา ดังนั้นการประมวลผลหยุดยังต้องทำงาน “แม้บริการที่คุณพึ่งลงไปแล้ว”8

ออกแบบการประมวลผลหยุดที่ไม่พึ่งระยะผ่อนหากเซฟที่แต่ละจุดตรวจการประมวลผลเพื่อให้ข้อมูลที่ยังไม่เซฟน้อยเสมอ คลีนอัปเมื่อการแจ้งหยุดมาถึงจบในไม่กี่วินาที การออกแบบที่เซฟทุกอย่างตอนออกจะไม่พอดีระยะผ่อน และการบังคับยุติเสียข้อมูลเซฟที่แต่ละจุดตรวจหยุด → เซฟเศษ → เสร็จเซฟทุกอย่างตอนออกหยุด → เซฟพลาดระยะผ่อนบังคับฆ่า → เสียข้อมูล

ภาพ 10: หากเซฟที่แต่ละจุดตรวจการประมวลผลเพื่อให้ข้อมูลที่ยังไม่เซฟน้อยเสมอ คลีนอัปเมื่อการแจ้งหยุดมาถึงจบในไม่กี่วินาที การออกแบบที่เซฟทุกอย่างตอนออกจะไม่พอดีระยะผ่อน และการบังคับยุติเสียข้อมูล

ใน .NET Worker Service (UseWindowsService) SERVICE_CONTROL_STOP และ SHUTDOWN ถูกแปลเป็นการหยุดโฮสต์ และ BackgroundService.StopAsync ถูกเรียก การอิมพลีเมนต์สต็อก ณ เวลาเขียนนี้ยอมรับครอบครัว STOP/SHUTDOWN หากต้องการ PRESHUTDOWN ด้วยจะต้องมีแฮนเดิลขยาย ไม่ว่าทางใด ทำให้ HostOptions.ShutdownTimeout ชัดและจบ StopAsync ในไม่กี่วินาที สำหรับการสร้างบริการโดยทั่วไป ดู “วิธีสร้างและดำเนินงานบริการ Windows

7. กู้คืนอัตโนมัติหลังรีสตาร์ต

บนพีซีอุปกรณ์หรือพีซีที่ไม่มีคนเฝ้า ขอบเขตออกแบบไม่ใช่เพียง “รอดชัตดาวน์” แต่ “กลับมาเองหลังรีสตาร์ต”

7.1. RegisterApplicationRestart และคอลแบ็กกู้คืน

หากเรียก RegisterApplicationRestart แอปถูกลงทะเบียนเป็นผู้สมัครรีสตาร์ตสำหรับ แครช (ข้อยกเว้นที่ไม่จัดการ) ไม่ตอบสนอง รีสตาร์ตแอปจากอัปเดต และรีสตาร์ต OS จากอัปเดต คุณลงทะเบียนอาร์กิวเมนต์บรรทัดคำสั่งสำหรับการรีสตาร์ตได้ ดังนั้นหากรวม “ไฟล์ใดเปิดอยู่” และ “จุดคืนใด” คุณกลับจากที่ค้างหลังรีสตาร์ตได้10

สเปกที่ต้องรับไว้มีดังนี้10

  • การลงทะเบียนต้องเสร็จ ก่อนปัญหาเกิด (ระหว่างจัดการ WM_QUERYENDSESSION คือโอกาสสุดท้ายในสถานการณ์อัปเดต)
  • เพื่อกันวงรีสตาร์ต โปรเซสที่รันน้อยกว่า 60 วินาทีไม่ถูกรีสตาร์ต
  • โปรเซสที่รันแบบยกระดับไม่ใช่ผู้สมัครรีสตาร์ตอัตโนมัติ (โปรเซสสร้างใหม่โดยไม่ได้รับความยินยอมยกระดับไม่ได้) การกู้คืนอัตโนมัติของแอปที่ต้องการการยกระดับออกแบบด้วยการเก็บ UI ที่สิทธิ์มาตรฐานและแยกงานที่มีสิทธิ์ในบริการ หรือเส้นทางเปิดชัด เช่น งาน Task Scheduler “Run with highest privileges”
  • รีสตาร์ตหลังแครชหรือแฮงผ่านความยินยอมของผู้ใช้ รีสตาร์ตหลังอัปเดตเป็นอัตโนมัติ
  • เพื่อกู้ข้ามรีสตาร์ต OS ฝ่ายที่ขอรีสตาร์ต (ตัวติดตั้งและคล้ายกัน) ต้องเรียก API ชัตดาวน์ด้วยธง EWX_RESTARTAPPS / SHUTDOWN_RESTARTAPPS

หากลงทะเบียน RegisterApplicationRecoveryCallback ด้วย WER (Windows Error Reporting) เรียกคอลแบ็กตอนแครชและให้ระยะผ่อนเพื่อเซฟข้อมูลที่กำลังทำ หากเซฟใช้เวลา อย่างไรก็ตาม ต้องเรียก ApplicationRecoveryInProgress ต่อไปภายในช่วงปิงที่ระบุตอนลงทะเบียน มิฉะนั้นงานกู้ถูกตัดกลางทาง เมื่อเซฟเสร็จ แจ้งความสมบูรณ์ด้วย ApplicationRecoveryFinished “การแทนที่ไฟล์ที่กำลังใช้และรีสตาร์ต” ตอนอัปเดตแอปเป็นอาณาเขตของ Restart Manager ครอบคลุมละเอียดใน “วิธีแทนที่ exe หรือ DLL ที่กำลังใช้

7.2. ARSO — เซ็นอินอัตโนมัติหลังรีสตาร์ตจากอัปเดต

หลังรีสตาร์ต Windows Update หากไม่มีใครเซ็นอิน แอปเซสชันผู้ใช้ไม่กลับมา สิ่งที่เติมช่องว่างนั้นคือ ARSO (Winlogon Automatic Restart Sign-On) เมื่อ Windows Update เริ่มรีสตาร์ต มันบันทึกข้อมูลรับรองของผู้ใช้เชิงโต้ตอบล่าสุดอย่างปลอดภัย ตั้งค่า Autologon และ หลังรีสตาร์ตเซ็นผู้ใช้นั้นเข้าอัตโนมัติแล้วล็อกหน้าจอ11 ยังมีคำสั่งเช่น shutdown /g ที่ขอรีสตาร์ตบวกการกลับของแอปที่ลงทะเบียน สภาพแวดล้อมบางแห่งปิดสิ่งนี้ด้วยนโยบายองค์กร (DisableAutomaticRestartSignOn และคล้ายกัน) ดังนั้นเมื่อออกแบบการกู้โดยไม่มีคนเฝ้า ตรวจการตั้งค่านี้เป็นชุด และหากพึ่งการเปิดอัตโนมัติในเซสชันผู้ใช้สำหรับงานพื้นหลังที่ต้องการเสมอ การเคลื่อนที่ถูกคือทำให้เป็นบริการ Windows ตั้งแต่ต้น

เส้นทางที่แอปกู้คืนอัตโนมัติหลังรีสตาร์ตหากลงทะเบียนด้วย RegisterApplicationRestart ก่อนปัญหาเกิด แอปถูกรีสตาร์ตหลังความยินยอมของผู้ใช้ตอนแครชหรือไม่ตอบสนอง และหลังเซ็นอินอัตโนมัติ ARSO และล็อกหน้าจอตอนรีสตาร์ตจากอัปเดต โปรเซสที่รันน้อยกว่า 60 วินาทีและโปรเซสที่ยกระดับอยู่นอกขอบเขตยินยอมRegisterApplicationRestartแครชหรือแฮงรีสตาร์ตจากอัปเดตรีสตาร์ตแอปARSO เซ็นอิน + ล็อกไม่: น้อยกว่า 60 วินาที / ยกระดับ

ภาพ 11: หากลงทะเบียนด้วย RegisterApplicationRestart ก่อนปัญหาเกิด แอปถูกรีสตาร์ตหลังความยินยอมของผู้ใช้ตอนแครชหรือไม่ตอบสนอง และหลังเซ็นอินอัตโนมัติ ARSO และล็อกหน้าจอตอนรีสตาร์ตจากอัปเดต โปรเซสที่รันน้อยกว่า 60 วินาทีและโปรเซสที่ยกระดับอยู่นอกขอบเขต

8. รอดจากการตัดไฟที่ไม่มีการแจ้ง — การออกแบบการเขียนและ UPS

8.1. การเขียนที่ “ไม่พังไม่ว่าจะถูกตัดเมื่อใด” — ไฟล์ชั่วคราว + ReplaceFile

เบรกเกอร์ตัด พาวเวอร์ซัพพลายเสีย หรือปลั๊กถูกดึง ไม่นำ WM_ENDSESSION หรือ PRESHUTDOWN มา ตราบที่คุณ “เขียนทับไฟล์ต้นฉบับในที่” สำหรับการตั้งค่าหรือผลวัด การตัดไฟกลางเขียนทิ้งไฟล์พังที่ผสมเก่าและใหม่ได้

รูปแบบมาตรฐานคือเขียนให้ครบลงไฟล์ชั่วคราวบนวอลุ่มเดียวกันแล้วสลับ ReplaceFile แพ็กเกจลำดับ “เซฟลงไฟล์ใหม่ → วางต้นฉบับไว้ข้าง → เปลี่ยนชื่อ → ลบ” เป็น API เดียว และยังนำแอตทริบิวต์ของไฟล์ต้นฉบับ เช่น เวลาสร้าง ACL และสตรีมสำรอง ไปด้วย (สามไฟล์ต้องอยู่บนวอลุ่มเดียวกัน)12 File.Replace ของ .NET เรียกสิ่งนี้ตามที่เป็น

ไหลการเซฟและกู้ด้วยไฟล์ชั่วคราวและ ReplaceFileตอนเซฟ เขียนให้ครบลงไฟล์ชั่วคราว ฟลัช และสลับด้วย ReplaceFile ทิ้งเนื้อหาเก่าใน .bak ตอนเปิดครั้งถัดไป ตรวจไฟล์หลักและถอยไป .bak หากพังตอนเปิดครั้งถัดไปตอนเซฟสมบูรณ์พังตัดไฟที่ขั้นใดตรวจไฟล์หลักใช้ตามที่เป็นถอยไป .bakฟลัชลงดิสก์เขียนไฟล์ชั่วคราวให้ครบReplaceFile → .bak

ภาพ 12: ตอนเซฟ เขียนให้ครบลงไฟล์ชั่วคราว ฟลัช และสลับด้วย ReplaceFile ทิ้งเนื้อหาเก่าใน .bak ตอนเปิดครั้งถัดไป ตรวจไฟล์หลักและถอยไป .bak หากพัง

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

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

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

ด้วยสิ่งนี้ การทำงานปกติทิ้งคุณให้อ่านได้เสมอทั้ง “ไฟล์เก่าครบ” หรือ “ไฟล์ใหม่ครบ” อย่างไรก็ตาม ReplaceFile เป็นปฏิบัติการเนมสเปซหลายขั้น และความเป็นอะตอมข้ามการตัดไฟสเปกไม่รับประกัน นั่นคือเหตุที่ตัวอย่างข้างบนเก็บสำรอง (.bak) — ฝั่งอ่านตรวจไฟล์หลักตอนเปิดและถอยไปสำรองหากพัง เป็นชุด คุณใช้สิ่งนี้กับล็อกแบบต่อท้ายหรือ CSV ไม่ได้ ดังนั้นพวกนั้นใช้รูปแบบที่สร้างการพังเข้าไป เช่น “หนึ่งบรรทัด = หนึ่งระเบียน และทิ้งบรรทัดสุดท้ายที่พังตอนอ่าน”

8.2. ความสำเร็จจาก WriteFile ไม่ใช่การถึงดิสก์

ข้อกำหนดอีกอย่างคือ แม้ WriteFile คืนความสำเร็จ ข้อมูลอาจยังอยู่ในแคชของ OS เท่านั้น Windows วางการอ่านและเขียนไฟล์บนบัฟเฟอร์ระบบและสะท้อนไปยังดิสก์เป็นระยะด้วยการเขียนแบบขี้เกียจ เพื่อให้ข้อมูลถึงดิสก์แน่นอน ฟลัชชัดด้วย FlushFileBuffers หรือระบุ FILE_FLAG_WRITE_THROUGH ที่ CreateFile เพื่อให้แต่ละการเขียนผ่านแคช เมทาดาทาของระบบไฟล์ถูกแคชเสมอ ดังนั้นการยืนยันเมทาดาทาก็ต้องการฟลัชหรือ write-through13

การเรียก FlushFileBuffers ทุกครั้งไม่มีประสิทธิภาพ อย่างไรก็ตาม และเอกสารก็สนับสนุนให้พิจารณา FILE_FLAG_NO_BUFFERING+WRITE_THROUGH แทนการเรียกบ่อย13 ในทางปฏิบัติ “ฟลัชเฉพาะที่จุดตรวจธุรกรรมหรือก่อนปิดไฟล์” เป็นการประนีประนอมที่สมจริง กลไกของชั้นนี้ — cache manager การเขียนแบบขี้เกียจ และข้อเท็จจริงที่ “ฉันฟลัชแล้วยังอาจไม่ถึงดิสก์” เพราะแคชฮาร์ดแวร์ — ครอบคลุมลึกใน “Cache Manager: เมื่อใด WriteFile ของคุณถึงดิสก์จริง ๆ

8.3. UPS และการเฝ้าแบตเตอรี่ — เปลี่ยนการตัดไฟเป็นชัตดาวน์

มาตรการจริงต่อการตัดไฟบนพีซีอุปกรณ์คือ UPS คิดบทบาทของ UPS ไม่ใช่ “หยุดไฟดับ” แต่เป็น เปลี่ยน “การตัดไฟที่ไม่มีการแจ้ง” เป็น “ชัตดาวน์ตามแผนที่มีการแจ้ง” การออกแบบเป็นการตั้งค่าสองขั้น

  1. ออกแบบระยะผ่อน: เวลาถือแบตเตอรี่ UPS > ผลรวมของ “ตรวจการสลับไปแบตเตอรี่ → คลีนอัปแอปและบริการ → OS ชัตดาวน์เสร็จ” หากการประมวลผลหยุดบริการช้า สมการนี้ไม่ตั้งอยู่อีก (หัวข้อ 6.2)
  2. การตรวจ: การสลับจาก AC ไปแบตเตอรี่ และการลดความจุที่เหลือ ถูกแจ้งด้วยอีเวนต์ PBT_APMPOWERSTATUSCHANGE แอปที่มีหน้าต่างรับเป็น WM_POWERBROADCAST บริการที่ไม่มีหน้าต่างประกาศ SERVICE_ACCEPT_POWEREVENT และรับเป็น SERVICE_CONTROL_POWEREVENT ใน HandlerEx (WM_POWERBROADCAST ไม่มาถึงแฮนเดิลควบคุมบริการ) เมื่อรับ เรียก GetSystemPowerStatus ตรวจ ACLineStatus (ว่าอยู่บน AC หรือไม่) และ BatteryLifePercent และนำไปสู่การขัดจังหวะการวัด เซฟ และขอชัตดาวน์15
ไหลของการเปลี่ยนการตัดไฟเป็นชัตดาวน์ตามแผนด้วย UPSเมื่อไฟดับสลับ UPS ไปแบตเตอรี่ PBT_APMPOWERSTATUSCHANGE ถูกแจ้ง สถานะพลังงานถูกตรวจ และเซฟบวกคำขอชัตดาวน์เปลี่ยนการตัดไฟที่ไม่มีการแจ้งเป็นชัตดาวน์ตามแผนที่มีการแจ้งไฟดับUPS ไปแบตเตอรี่PBT_APMPOWERSTATUSCHANGEGetSystemPowerStatusขัดจังหวะและเซฟขอชัตดาวน์ไหลแจ้งปกติ(3–6)

ภาพ 13: เมื่อไฟดับสลับ UPS ไปแบตเตอรี่ PBT_APMPOWERSTATUSCHANGE ถูกแจ้ง สถานะพลังงานถูกตรวจ และเซฟบวกคำขอชัตดาวน์เปลี่ยนการตัดไฟที่ไม่มีการแจ้งเป็นชัตดาวน์ตามแผนที่มีการแจ้ง

UPS ที่ต่อ USB ทั่วไปปรากฏต่อ Windows เป็นแบตเตอรี่ ดังนั้นคุณตรวจด้วย API มาตรฐานนี้ได้ หากซอฟต์แวร์จัดการของผู้ขายมีฟีเจอร์ “ชัตดาวน์ OS ที่เหลือ N%” ให้ตรวจด้วยว่าเกณฑ์เรียงกับเวลาคลีนอัปของแอป การรีซูมจากสลีปหรือไฮเบอร์เนต และปัญหารันยาว เป็นแกนแยก ครอบคลุมใน “สลีป ไฮเบอร์เนต Modern Standby และแอปที่รันยาว

9. วิธียืนยัน — ลองชัตดาวน์อย่างปลอดภัย

การจัดการชัตดาวน์มักกลายเป็นเราเขียนแล้วแต่ไม่เคยลองภายใต้เงื่อนไขเทียบเท่าโปรดักชัน เก็บขั้นตอนสำหรับยืนยันอย่างปลอดภัย

  • ลองบนเครื่องทดสอบหรือ VM: อย่าลองครั้งแรกบนพีซีอุปกรณ์โปรดักชัน ในสภาพแวดล้อมทดสอบที่มีจุดตรวจ Hyper-V ให้ทำซ้ำชัตดาวน์ รีสตาร์ต และการปิดไฟ VM การปิดไฟของ VM ทำซ้ำเพียงเกสต์ OS หยุดโดยไม่แจ้ง ไม่ทำซ้ำการหายของแคชดิสก์กายภาพ หากส่งเป็นพีซีอุปกรณ์ การตรวจสุดท้ายคือการทดสอบตัดไฟจริงบนฮาร์ดแวร์เทียบเท่าโปรดักชัน
  • ตรวจเร็วด้วยเซ็นเอาต์: เส้นทาง WM_QUERYENDSESSION ถึง WM_ENDSESSION รันตอนเซ็นเอาต์ด้วย จึงยืนยันโค้ดคลีนอัปบนเครื่องพัฒนาได้1
  • ลองชัตดาวน์เต็มและไฮบริดแยก: ลอง shutdown /s /t 0 (เต็ม) shutdown /s /hybrid /t 0 (ไฮบริด) และ shutdown /r /t 0 (รีสตาร์ต) แต่ละอย่าง2
  • วัดว่าคลีนอัปใช้เวลานานเท่าใด: เขียนตราประทับเวลาลงล็อกที่ต้นและปลายฟังก์ชันคลีนอัป และวัดว่าพอดี 5 วินาทีหรือระยะผ่อนที่ตั้งสำหรับบริการหรือไม่
การดำเนินการที่ยืนยันและสิ่งที่แต่ละอย่างยืนยันได้เซ็นเอาต์ตรวจเส้นทางแจ้ง คำสั่งชัตดาวน์ยืนยันเส้นทางโปรดักชัน การปิดไฟ VM ทดสอบการหยุดกะทันหัน และการตัดไฟกายภาพเป็นการตรวจสุดท้ายเซ็นเอาต์เส้นทาง QUERY ถึง ENDSESSIONshutdown /s /hybrid /rเส้นทางโปรดักชันบวกระยะผ่อนปิดไฟ VMเกสต์หยุดกะทันหันตัดไฟกายภาพรวมที่เก็บ(สุดท้าย)

ภาพ 14: เซ็นเอาต์ตรวจเส้นทางแจ้ง คำสั่งชัตดาวน์ยืนยันเส้นทางโปรดักชัน การปิดไฟ VM ทดสอบการหยุดกะทันหัน และการตัดไฟกายภาพเป็นการตรวจสุดท้าย

สำหรับการแยกหลังเหตุการณ์ ล็อกอีเวนต์ System มีประโยชน์ ตอนชัตดาวน์หรือรีสตาร์ตปกติ อีเวนต์ ID 1074 ถูกบันทึก ตอนตัดไฟกะทันหันหรือแครชไม่มี 1074 และในการบูตครั้งถัดไปอีเวนต์ ID 41 Kernel-Power และ 6008 ถูกบันทึก14

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

หาก 1074 แสดงรีสตาร์ตโดย Windows Update และข้อมูลของแอปพัง ปัญหาคือโค้ดคลีนอัป 6008 และ 41 แสดงเพียงชัตดาวน์ที่ไม่คาดคิด พวกมันถูกบันทึกสำหรับหน้าจอฟ้าหรือรีเซ็ตบังคับด้วย ไม่ใช่เพียงการตัดไฟ หาก BugcheckCode ของ 41 ไม่เป็นศูนย์คือแครช หากเป็น 0 และไม่มีดัมป์หน่วยความจำด้วย การตัดไฟน่าจะเป็น แยกสาเหตุจากข้อมูลรอบข้าง และเมื่อรู้ว่าเป็นการตัดไฟ การออกแบบการเขียนในบทที่ 8 และ UPS คือขั้นถัดไป

10. สรุป

  • ชัตดาวน์คืออีเวนต์ปกติที่จะมา ไม่ช้าก็เร็ว ระยะผ่อนหลังการแจ้งโดยหลักแล้วประมาณ 5 วินาทีเท่านั้น ดังนั้นข้อกำหนดเกิดคือออโตเซฟบ่อยเพื่อให้สิ่งที่ทำตอนออกเหลือน้อย
  • บนไคลเอนต์ OS จาก Windows 8 เป็นต้นไป หาก fast startup เปิด Shut down คือ hybrid shutdown และเคอร์เนลเพียงไฮเบอร์เนต การรีเซ็ตเต็มมีเพียง Restart เขียน Restart ลงในขั้นตอนเหตุการณ์
  • แอป GUI คืน TRUE ทันทีให้ WM_QUERYENDSESSION และทำคลีนอัปที่ยืนยันแล้วใน WM_ENDSESSION FormClosing และ SessionEnding ของ WinForms และ WPF สอดคล้องกับระยะสอบถาม ดังนั้นสิ่งที่ทำที่นั่นคือเซฟสแนปช็อตแบบ idempotent อย่างมาก อย่าวางกล่องโต้ตอบระหว่างชัตดาวน์
  • ปฏิบัติการที่ขัดจังหวะไม่ได้จริงถูกป้องกันด้วยการแสดงเหตุผลด้วย ShutdownBlockReasonCreate อย่างไรก็ตามไม่มีที่ใดรับประกันว่าบล็อกได้
  • แอปคอนโซลรับการแจ้งด้วย SetConsoleCtrlHandler บริการด้วย SERVICE_ACCEPT_PRESHUTDOWN และ SHUTDOWN ระยะผ่อน PRESHUTDOWN เริ่มต้นบน OS ปัจจุบันคือ 10 วินาที ใน .NET หยุดพึ่ง ProcessExit และย้ายไปเส้นทางมาตรฐานของโมเดลแอป
  • การกู้หลังรีสตาร์ตทำโดยไม่มีคนเฝ้าได้ด้วย RegisterApplicationRestart บวกคอลแบ็กกู้และ ARSO
  • การตัดไฟไม่มีการแจ้ง เตรียมด้วยการสลับไฟล์ชั่วคราวบวก ReplaceFile เป็นชุดกับสำรองและการตรวจตอนโหลด ฟลัชที่จุดตรวจ และ UPS ที่เปลี่ยนการตัดไฟเป็นชัตดาวน์ตามแผน
ภาพรวมของการจัดการชัตดาวน์สำหรับการจบที่มีการแจ้ง ตอบด้วยคลีนอัปไม่กี่วินาทีและกู้หลังรีสตาร์ต สำหรับการตัดไฟที่ไม่มีการแจ้ง เตรียมด้วยการเขียนที่ไม่พังและ UPSมีการแจ้งไม่มีการแจ้งจบอย่างไรคลีนอัปไม่กี่วินาที(3 ถึง 6)เขียนปลอดภัยบวก UPS(8)กู้อัตโนมัติหลังรีสตาร์ต(7)ยืนยันบนฮาร์ดแวร์(9)

ภาพ 15: สำหรับการจบที่มีการแจ้ง ตอบด้วยคลีนอัปไม่กี่วินาทีและกู้หลังรีสตาร์ต สำหรับการตัดไฟที่ไม่มีการแจ้ง เตรียมด้วยการเขียนที่ไม่พังและ UPS

  • ยืนยันอย่างปลอดภัยบน VM และด้วยเซ็นเอาต์ และแยกหลังเหตุการณ์ด้วยอีเวนต์ ID 1074 41 และ 6008

ครั้งถัดไปที่คุณเพิ่มฟีเจอร์ให้แอป ถามตัวเองครั้งหนึ่ง: หาก WM_ENDSESSION มาถึงกลางงานนี้ หรือไฟถูกดึง สิ่งที่เหลือในการเปิดครั้งถัดไปคืออะไร การเขียนคำตอบนั้นเข้าไปในการออกแบบคือเส้นทางสั้นที่สุดที่จะไม่เสียเช้ามายืนหน้าพีซีอุปกรณ์กอดหัว

บทความที่เกี่ยวข้อง

ด้านให้คำปรึกษาที่เกี่ยวข้อง

KomuraSoft LLC รับออกแบบและอิมพลีเมนต์มาตรการชัตดาวน์และการตัดไฟสำหรับแอปพีซีอุปกรณ์และแอปที่รันยาว การสอบสวนสาเหตุรากของการเสียข้อมูลและเหตุการณ์หยุดเช้าที่เริ่มจากรีสตาร์ต Windows Update หรือเซ็นเอาต์ และการตรวจทานการออกแบบการประมวลผลหยุดของบริการ Windows และการกู้คืนอัตโนมัติ เริ่มจากขั้นที่ดูเหมือนพังทุกครั้งที่ชัตดาวน์และไม่รู้จะเริ่มตรงไหนก็ได้

ลิงก์อ้างอิง

  1. Microsoft Learn, WM_QUERYENDSESSION message. ว่าด้วย WM_QUERYENDSESSION ถูกส่งตอนจบเซสชันและแอปควรคืน TRUE และเคารพเจตนาของผู้ใช้ ค่าเริ่มต้นของ DefWindowProc ก็เป็น TRUE ว่าด้วยคลีนอัปควรถูกเลื่อนจน WM_ENDSESSION ว่าด้วยหลัง 5 วินาทีระบบแสดง UI สำหรับแอปที่กำลังป้องกันชัตดาวน์และผู้ใช้บังคับยุติได้ ความหมายของบิต ENDSESSION_LOGOFF CLOSEAPP CRITICAL ใน lParam ว่าด้วยชัตดาวน์และรีสตาร์ตแยกไม่ได้ และว่าด้วยควรเซฟข้อมูลบ่อยเพื่อให้ต้องเซฟตอนออกน้อยลง  2 3 4 5 6 7

  2. Microsoft Learn, Fast startup causes hibernation or shutdown to fail in Windows 10 or Windows 8.1. ว่าด้วยเมื่อ fast startup เซสชันเคอร์เนลไม่ถูกปิดและถูกถือเป็นไฮเบอร์เนต และสถานะเคอร์เนลกับไดรเวอร์ถูกบันทึกลง hiberfil.sys ว่าด้วย Restart ทำบูตเต็มเสมอเพราะต้องการสถานะ Windows ใหม่สมบูรณ์ ว่าด้วย fast startup เปิดโดยค่าเริ่มต้นและการปิดไม่แนะนำ และว่าด้วยค่าเริ่มต้นของ Shutdown.exe คือชัตดาวน์เต็ม โดยตัวเลือก hybrid ให้พฤติกรรมไฮบริด  2 3 4 5

  3. Microsoft Learn, ShutdownBlockReasonCreate function (winuser.h). ว่าด้วยเรียกตอนเริ่มปฏิบัติการที่ขัดจังหวะไม่ได้เพื่อลงทะเบียนสตริงเหตุผลและเรียก ShutdownBlockReasonDestroy เมื่อเสร็จ ว่าด้วยเรียกได้เฉพาะจากเธรดที่สร้างหน้าต่าง และว่าด้วยผู้ใช้จะอ่านเหตุผลเพียงไม่กี่วินาทีจึงควรสั้นและชัด  2 3

  4. Microsoft Learn, Shutdown Changes for Windows Vista. ว่าด้วยการตอบ WM_QUERYENDSESSION และ WM_ENDSESSION เลื่อนได้ 5 วินาทีต่อข้อความและผู้ใช้เลือกทำต่อหรือยกเลิกได้ ว่าด้วยแอปคอนโซลหรือแอปที่ไม่มีหน้าต่างมองเห็นยกเลิกชัตดาวน์ไม่ได้และถูกยุติอัตโนมัติหลังไม่ตอบ 5 วินาทีหรือตอบ FALSE ว่าด้วยหากต้องบล็อกควรลงทะเบียนเหตุผลด้วย ShutdownBlockReasonCreate และว่าด้วยแอปต้องไม่พึ่งการบล็อกชัตดาวน์ได้  2 3 4

  5. Microsoft Learn, HandlerRoutine callback function. อีเวนต์ CTRL_C BREAK CLOSE LOGOFF SHUTDOWN ที่แฮนเดิลที่ลงทะเบียนด้วย SetConsoleCtrlHandler รับ ว่าด้วยหมดเวลาเริ่มต้นของ CTRL_CLOSE_EVENT ประมาณ 5000 มิลลิวินาทีและของ CTRL_SHUTDOWN_EVENT บนโปรเซสบริการประมาณ 20000 มิลลิวินาที ว่าด้วย CTRL_LOGOFF และ SHUTDOWN_EVENT รับโดยพื้นฐานเฉพาะบริการเพราะแอปเชิงโต้ตอบถูกยุติตอนล็อกออฟ และว่าด้วยแฮนเดิลรันบนเธรดแยก  2 3

  6. Microsoft Learn, SetConsoleCtrlHandler function. ว่าด้วยโปรเซสที่โหลด gdi32.dll หรือ user32.dll ถูกถือเป็นแอป Windows และแฮนเดิล CTRL_LOGOFF_EVENT และ CTRL_SHUTDOWN_EVENT ไม่ถูกเรียก ว่าด้วยวิธีแก้คือสร้างหน้าต่างซ่อนและจัดการ WM_QUERYENDSESSION และ WM_ENDSESSION และว่าด้วยฟังก์ชันคอนโซลอาจทำงานไม่ถูกต้องระหว่างจัดการสัญญาณ  2

  7. Microsoft Learn, .NET runtime no longer provides default termination signal handlers. ว่าด้วยจาก .NET 10 รันไทม์ไม่ให้แฮนเดิลเริ่มต้นสำหรับ Windows CTRL_SHUTDOWN_EVENT และ CTRL_CLOSE_EVENT ซึ่งเทียบเท่า Unix SIGTERM และ SIGHUP ว่าด้วยการจัดการเริ่มต้นของ OS ยุติแอปทันทีและ AppDomain.ProcessExit และ AssemblyLoadContext.Unloading ไม่ยิงอีก และว่าด้วยควรลงทะเบียนการจัดการสัญญาณที่เหมาะกับโมเดลแอปในไลบรารีระดับสูงหรือในโค้ดแอป  2

  8. Microsoft Learn, Service Control Handler Function. ว่าด้วยบริการที่ประกาศ PRESHUTDOWN รับการแจ้งก่อน SHUTDOWN ระยะผ่อนเริ่มต้นประมาณ 20 วินาทีและเพดานคือ WaitToKillServiceTimeout ซึ่งไม่ควรขยาย แฮนเดิลควบคุมควรคืนภายใน 30 วินาที รายงาน STOP_PENDING และทิ้งงานยาวให้เธรดอื่น คลีนอัปควรจบเร็วโดยคำนึงถึง UPS และ SCM ตอนชัตดาวน์โดยค่าเริ่มต้นไม่พิจารณาการพึ่งพา  2 3 4 5

  9. Microsoft Learn, SERVICE_PRESHUTDOWN_INFO structure (winsvc.h). ว่าด้วยหลังการแจ้ง PRESHUTDOWN SCM รอจนกว่าบริการจะหยุดหรือหมดเวลา ค่าหมดเวลาเริ่มต้นคือ 10 วินาทีจาก Windows 10 Creators Update build 15063 เป็นต้นไป และ 3 นาทีก่อนนั้น ตั้งค่าด้วย ChangeServiceConfig2 และอัปเดตสถานะต่อได้ระหว่าง SERVICE_STOP_PENDING  2

  10. Microsoft Learn, RegisterApplicationRestart function (winbase.h). ว่าด้วยลงทะเบียนรีสตาร์ตสำหรับแครช ไม่ตอบสนอง อัปเดต และรีสตาร์ตเครื่องที่มากับอัปเดต ระบุอาร์กิวเมนต์บรรทัดคำสั่งได้ ต้องลงทะเบียนก่อนปัญหาเกิดและระหว่างจัดการ WM_QUERYENDSESSION คือโอกาสสุดท้ายในสถานการณ์อัปเดต โปรเซสที่รันน้อยกว่า 60 วินาทีไม่ถูกรีสตาร์ต รีสตาร์ตหลังแครชหรือแฮงผ่านความยินยอมของผู้ใช้ และการข้ามรีสตาร์ต OS ต้องการชัตดาวน์ด้วย EWX_RESTARTAPPS หรือ SHUTDOWN_RESTARTAPPS  2 3

  11. Microsoft Learn, Winlogon automatic restart sign-on (ARSO). ว่าด้วย Windows Update บันทึกข้อมูลรับรองผู้ใช้เชิงโต้ตอบล่าสุดและตั้ง Autologon หลังรีสตาร์ตเซ็นเข้าอัตโนมัติแล้วล็อกเซสชัน ข้อมูลรับรองถูกลบหลังเซ็นเข้าสำเร็จ และตั้งค่าด้วย Group Policy เช่น DisableAutomaticRestartSignOn ได้  2

  12. Microsoft Learn, ReplaceFileW function (winbase.h). ว่าด้วย ReplaceFile แพ็กเกจหลายขั้นที่เทียบเท่าเซฟลงไฟล์ใหม่ เปลี่ยนชื่อต้นฉบับชั่วคราว เปลี่ยนชื่อไฟล์ใหม่ และลบต้นฉบับเป็นฟังก์ชันเดียว ว่าด้วยรักษาแอตทริบิวต์ของไฟล์ต้นฉบับ เช่น เวลาสร้าง DACL การเข้ารหัส การบีบอัด และ named streams และว่าด้วยไฟล์สำรอง ไฟล์ที่ถูกแทนที่ และไฟล์แทนที่ต้องอยู่บนวอลุ่มเดียวกัน  2

  13. Microsoft Learn, File Caching. ว่าด้วยการเขียนไปบนแคชระบบโดยค่าเริ่มต้นและสะท้อนไปยังดิสก์ด้วยการเขียนแบบขี้เกียจ FILE_FLAG_WRITE_THROUGH เขียนทันที FlushFileBuffers ฟลัชชัดได้ และเมทาดาทาของระบบไฟล์ถูกแคชเสมอจึงต้องฟลัชหรือ write-through เพื่อยืนยัน  2 3

  14. Microsoft Learn, Troubleshoot unexpected reboots using system event logs. ว่าด้วยรีสตาร์ตปกติบันทึกอีเวนต์ ID 1074 ซึ่งโปรเซสใดเริ่มชัตดาวน์ เพื่อใคร และด้วยเหตุผลใด ว่าด้วยรีสตาร์ตที่ไม่คาดคิดบันทึกอีเวนต์ ID 41 Kernel-Power และ 6008 และว่าด้วย ID เหล่านี้แยกชนิดของรีสตาร์ตได้  2

  15. Microsoft Learn, PBT_APMPOWERSTATUSCHANGE event. ว่าด้วยอีเวนต์นี้ถูกแจ้งผ่าน WM_POWERBROADCAST ตอนสลับแบตเตอรี่กับ AC หรือความจุลด และเมื่อรับควรเรียก GetSystemPowerStatus และตรวจ ACLineStatus BatteryFlag และ BatteryLifePercent 

บทความล่าสุดที่มีแท็กเดียวกัน เพื่อเจาะลึกหัวข้อใกล้เคียง

Win32 Thread Pool API — คอนเคอร์เรนซีโดยไม่สร้างเธรดเอง ด้วย CreateThreadpoolWork

โค้ดเนทีฟของคุณกระจายการเรียก CreateThread ไปทั่วหรือไม่ บทความนี้อธิบาย Win32 thread pool API ที่ออกแบบใหม่ใน Vista — ออบเจ็กต์สี่ชนิด w...

Named Pipe ในทางปฏิบัติ — IPC มาตรฐานของ Windows ตั้งแต่การออกแบบถึงความปลอดภัย

คู่มือเชิงปฏิบัติเรื่อง named pipe ซึ่งเป็นการสื่อสารระหว่างโปรเซสมาตรฐานของ Windows บทความนี้จัดระเบียบจากแหล่งปฐมภูมิ ทั้งการเลือกระหว่...

แอปที่พังเมื่อรีซูมจากสลีป — อีเวนต์พลังงานของ Windows ทำงานอย่างไร และสร้างแอปธุรกิจที่รอดได้อย่างไร

เปิดแล็ปท็อปแล้วการเชื่อมต่อของแอปธุรกิจตาย — สาเหตุคือการออกแบบที่ไม่เคยคิดถึงสลีป บทความนี้ครอบคลุมลำดับการแจ้ง WM_POWERBROADCAST พฤติก...

DllMain และ Loader Lock — เหตุผลจริงที่ถูกบอกให้ "อย่าทำอะไรในการเริ่มต้น DLL"

ทำไมคุณต้องไม่เรียก LoadLibrary หรือซิงโครไนซ์กับเธรดอื่นจาก DllMain บทความนี้อธิบายจากแหล่งปฐมภูมิว่า loader lock ทำให้การแจ้ง DLL ทุกคร...

"ไม่ตอบสนอง" คืออะไรจริง ๆ — Windows ตัดสินว่าแอปแฮงอย่างไร และออกแบบอย่างไรให้ไม่แฮง

"ไม่ตอบสนอง" ของ Windows คือกลไกที่ OS ตัดสินว่าหน้าต่างไม่ได้ดึงข้อความเป็นเวลา 5 วินาทีแล้วแทนที่ด้วยหน้าต่างโกสต์ บทความนี้ครอบคลุมภาย...

หน้าเหล่านี้วางหัวข้อไว้ในบริบทที่กว้างขึ้นของบริการและการตัดสินใจ

บทความนี้เกี่ยวข้องโดยตรงกับบริการต่อไปนี้

คำถามที่พบบ่อย

คำถามที่มักพบในการปรึกษาเกี่ยวกับหัวข้อของบทความนี้

ปัญหาที่ไม่หายหลัง "Shut down" หายหลัง "Restart" ทำไม
บนไคลเอนต์ OS จาก Windows 8 เป็นต้นไป เมื่อ fast startup เปิด (ค่าเริ่มต้นบนพีซีส่วนใหญ่ที่รองรับไฮเบอร์เนต) "Shut down" ใช้กลไกเรียก hybrid shutdown ผู้ใช้ถูกเซ็นเอาต์ แต่สถานะเคอร์เนลและไดรเวอร์ถูกบันทึกลงไฟล์ไฮเบอร์เนตและคืนตามที่เป็นในการบูตครั้งถัดไป กล่าวคือแกนของ OS ไม่ได้ถูกรีเซ็ต "Restart" ในทางกลับกันทำการบูตเต็มเสมอ ดังนั้นปัญหาไดรเวอร์และบริการถูกรีเซ็ต เขียน "Restart" ลงในขั้นตอนแยก ไม่ใช่ "Shut down แล้วเปิดใหม่" หากต้องการชัตดาวน์เต็มจากบรรทัดคำสั่ง ใช้ 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 ไม่ยิงในกรณีเหล่านั้น อิมพลีเมนต์คลีนอัปบนเส้นทางแจ้งที่ตรงกับโมเดลแอป: แอป GUI ใช้ FormClosing หรือ SessionEnding (เหล่านั้นเป็นการแจ้งระยะสอบถาม จึงจำกัดเฉพาะเซฟแบบ idempotent คลีนอัปที่รันได้เฉพาะหลังเซสชันถูกยืนยันอยู่ในฮุก WM_ENDSESSION) Generic Host / Worker Service ใช้ IHostApplicationLifetime และ StopAsync แอปคอนโซลใช้ SetConsoleCtrlHandler หรือ PosixSignalRegistration
จะกันไฟล์ไม่ให้เสียจากการตัดไฟกะทันหันอย่างไร
การตัดไฟไม่มีการแจ้งเลย ดังนั้นทางเดียวคือเขียนในทางที่ไม่พังไม่ว่าไฟจะขาดเมื่อใด เส้นฐานคือไม่เขียนทับไฟล์ต้นฉบับในที่: เขียนให้ครบลงไฟล์ชั่วคราวบนวอลุ่มเดียวกัน ฟลัช และสลับด้วย ReplaceFile (File.Replace ใน .NET) ในการทำงานปกติสิ่งนี้ทิ้งคุณให้อ่านได้ทั้งไฟล์เก่าครบหรือไฟล์ใหม่ครบ แต่ความเป็นอะตอมของ ReplaceFile ข้ามการตัดไฟสเปกไม่รับประกัน จึงเก็บสำรอง (อาร์กิวเมนต์ที่สาม) และอิมพลีเมนต์การกู้ตอนโหลดที่ตรวจไฟล์หลักและถอยไปสำรองหากพัง นอกจากนี้ ความสำเร็จจาก WriteFile ไม่ได้หมายความว่าข้อมูลถึงดิสก์แล้ว จึงที่จุดตรวจสำคัญยืนยันการเขียนด้วย FlushFileBuffers หรือ FILE_FLAG_WRITE_THROUGH บนพีซีอุปกรณ์การตั้งค่ามาตรฐานคือรวมกับ UPS ตรวจการสลับไปแบตเตอรี่ และนำไปสู่ชัตดาวน์ปลอดภัย

โปรไฟล์ผู้เขียน

หน้าแนะนำผู้เขียนบทความ

Go Komura

ผู้แทนของ KomuraSoft LLC

เชี่ยวชาญด้านการพัฒนาซอฟต์แวร์ Windows ที่ปรึกษาเทคนิค และการตรวจสอบบั๊ก โดยเฉพาะโปรเจกต์ที่มีระบบเดิมและบั๊กที่ทำซ้ำได้ยาก

ลิงก์สาธารณะ

กลับไปยังบล็อก