Shutdown Windows dilihat dari aplikasi Anda — bertahan dari notifikasi keluar, restart, dan kehilangan daya dengan benar

· · Windows, Shutdown, Pengembangan Windows, Layanan Windows, PC perangkat, Integritas data, Operasi berjalan lama, UPS

“Setelah restart semalam oleh Windows Update, aplikasi pengukuran pada PC perangkat mati di tengah tulis, dan pagi harinya berkas pengukuran rusak.” “Seseorang keluar dari PC bersama dan mengeluh bahwa suntingan yang belum disimpan hilang.” — Untuk aplikasi Windows yang berjalan lama, dua konsultasi ini klasik.

Yang dimiliki kedua situs bersama adalah memperlakukan shutdown sebagai “event abnormal yang seharusnya tidak terjadi”. Pada kenyataannya, walaupun, dari restart otomatis Windows Update, keluar pengguna, dan shutdown yang dimulai UPS sampai kehilangan daya tanpa pengumuman, event yang memotong eksekusi dari luar aplikasi akan datang, cepat atau lambat. Anda tidak dapat mencegahnya datang. Yang dapat Anda cegah adalah “kehilangan data ketika ia datang”.

Untungnya, Windows punya mekanisme yang memberitahu aplikasi sebelum shutdown, untuk aplikasi GUI, aplikasi konsol, dan layanan sama. Ditujukan kepada staf IT di usaha kecil dan menengah serta pengembang aplikasi Windows (terutama aplikasi PC perangkat dan yang berjalan lama), artikel ini menata cara menerima notifikasi itu, cara merancang pembersihan yang “selesai dalam beberapa detik”, pemulihan otomatis setelah restart, dan cara bersiap untuk kehilangan daya yang tidak membawa notifikasi — semuanya berlandaskan sumber primer Microsoft Learn per Agustus 2026.

1. Kesimpulan lebih dulu

  • Rancang shutdown sebagai “event normal yang akan datang, cepat atau lambat”. Waktu yang dapat Anda pakai setelah menerima notifikasi, pada prinsipnya, hanya sekitar 5 detik, jadi desain yang buru-buru menyimpan semuanya di tempat akan ambruk. Prasyaratnya adalah autosave sering agar “delta yang harus disimpan saat shutdown” tetap kecil.1
  • Pada OS klien dari Windows 8 ke atas, ketika fast startup diaktifkan (bawaan pada kebanyakan PC yang mendukung hibernasi), “Shut down” adalah hybrid shutdown, dan kernel hanya berhibernasi. Satu-satunya yang di-reset penuh adalah “Restart”. Itulah alasan sungguhan “saya mematikannya dan tidak membaik, lalu saya restart dan ia membaik”.2
  • Aplikasi GUI harus mengembalikan TRUE segera ke WM_QUERYENDSESSION, dan melakukan pembersihan di WM_ENDSESSION. Pada prinsipnya Anda tidak boleh mengembalikan FALSE (menolak).1
  • Hanya ketika Anda sungguh punya operasi yang tidak dapat diinterupsi Anda harus menampilkan alasan dengan ShutdownBlockReasonCreate. Bahkan lalu pengguna dan OS dapat memaksa lanjut, jadi desain yang mengasumsikan “kita dapat memblokir” tidak bertahan.34
  • Aplikasi konsol menerima notifikasi dengan SetConsoleCtrlHandler. Masa tenggang bahkan lebih pendek — bawaan 5 detik untuk tutup konsol. Ada juga jebakan: dalam proses yang telah memuat gdi32.dll atau user32.dll, sebagian event ini tidak datang.56
  • Pembersihan yang mengandalkan AppDomain.ProcessExit milik .NET tidak berjalan, dari .NET 10 ke atas, pada jalur di mana proses “diakhiri dari luar”. Pada keluar normal seperti kembali dari Main ia tetap berjalan seperti sebelumnya, tetapi karena runtime tidak lagi menyediakan penanganan bawaan untuk sinyal terminasi seperti tutup konsol dan shutdown, pembersihan pada jalur itu harus pindah ke notifikasi yang cocok dengan model aplikasi.7
  • Layanan Windows dapat menerima SERVICE_ACCEPT_PRESHUTDOWN lebih awal, dan dengan masa tenggang yang dapat dikonfigurasi, daripada SERVICE_ACCEPT_SHUTDOWN (sekitar 20 detik masa tenggang). Timeout PRESHUTDOWN bawaan, walaupun, dipendekkan menjadi 10 detik dari Windows 10 Creators Update ke atas, jadi bagaimanapun Anda membutuhkan desain yang tidak terlalu bersandar pada masa tenggang.89
  • Pemulihan otomatis setelah restart dapat dicapai dengan menggabungkan RegisterApplicationRestart dengan ARSO (sign-on otomatis). Jalur pemulihan disediakan untuk crash, tidak merespons, dan restart yang didorong pembaruan.1011
  • Kehilangan daya tidak membawa notifikasi sama sekali. Pola standar adalah menulis lengkap ke berkas sementara, flush, dan tukar dengan ReplaceFile, tetapi karena ReplaceFile juga tidak menjamin atomisitas melewati kehilangan daya, jalur pemulihan cadangan (.bak) plus validasi saat muat adalah bagian dari set. Isolasi setelah-fakta dapat dilakukan dari event log (1074/41/6008).121314

Dalam satu kalimat, kesimpulan artikel ini adalah: “selalu jaga state dari mana Anda dapat menutup toko dalam beberapa detik ketika notifikasi tiba, dan tulis dengan cara yang tidak rusak bahkan pada kehilangan daya yang tidak membawa notifikasi”.

2. Apa yang terjadi saat shutdown — empat cara berakhir

2.1. Keluar, shutdown, restart, dan kehilangan daya

Dari sudut pandang aplikasi, yang penting adalah dua sumbu: “bagaimana sesi pengguna berakhir” dan “apa yang terjadi pada kernel”.

Operasi Sesi pengguna Kernel dan driver Notifikasi ke aplikasi
Keluar Berakhir Tetap berjalan WM_QUERYENDSESSION (ENDSESSION_LOGOFF) → WM_ENDSESSION
Shutdown (fast startup aktif) Berakhir Berhibernasi (disimpan ke hiberfil.sys) WM_QUERYENDSESSION → WM_ENDSESSION, (PRE)SHUTDOWN ke layanan
Restart Berakhir Berakhir sepenuhnya; boot berikutnya adalah boot penuh Sama seperti di atas
Kehilangan daya Hilang segera Hilang segera Tidak ada

Keluar dan shutdown, dari sudut pandang aplikasi, hampir event yang sama. Jika bit ENDSESSION_LOGOFF disetel di lParam WM_QUERYENDSESSION itu keluar; jika 0 itu shutdown atau restart (Anda tidak dapat membedakan keduanya).1 Dengan kata lain, kepuasan diri “ini hanya keluar, kita akan baik-baik saja” tidak bertahan, dan desain yang benar adalah kode pembersihan yang sama dipanggil.

Empat cara berakhir, dan notifikasi ke aplikasiKeluar, shutdown, dan restart mengirimkan notifikasi WM_QUERYENDSESSION ke WM_ENDSESSION, dan pembersihan selesai dalam beberapa detik. Hanya kehilangan daya yang tidak punya notifikasi sama sekali, jadi Anda bersiap dengan desain tulis di Bab 8 dan UPSKeluarQUERY → ENDSESSIONShutdownRestartKehilangan dayaTanpa notifikasi: tulis + UPSPembersihan dalam detik

Gambar 1: Keluar, shutdown, dan restart mengirimkan notifikasi WM_QUERYENDSESSION ke WM_ENDSESSION, dan pembersihan selesai dalam beberapa detik. Hanya kehilangan daya yang tidak punya notifikasi sama sekali, jadi Anda bersiap dengan desain tulis di Bab 8 dan UPS.

2.2. Alasan sungguhan “saya mematikannya dan tidak membaik” — hybrid shutdown

Baris yang mudah terlewat adalah baris kedua di tabel. Pada OS klien dari Windows 8 ke atas, fast startup (hybrid shutdown) diaktifkan secara bawaan pada PC yang mendukung hibernasi, dan perilaku “Shut down” berubah. Keluar sesi pengguna tetap terjadi seperti biasa, tetapi sesi kernel tidak ditutup; ia disimpan, driver perangkat dan semuanya, ke berkas hibernasi (hiberfil.sys) dan dipulihkan apa adanya pada boot berikutnya. Itu membuat startup lebih cepat, tetapi state kernel dan driver bertahan bahkan setelah Anda memotong daya.2 Ini, walaupun, adalah perilaku bersyarat. Di lingkungan di mana hibernasi itu sendiri dinonaktifkan (powercfg /hibernate off), di mana kebijakan atau Power Options telah mematikan fast startup, dan pada Windows Server, shutdown adalah shutdown penuh konvensional. Anda dapat melihat cara PC tertentu berjalan dari kotak centang “Turn on fast startup” di Power Options, atau dari apakah powercfg /a (keadaan tidur yang tersedia) mencantumkan “Fast Startup”.

Apa yang terjadi pada kernel pada operasi Shut downOperasi Shut down belah menjadi shutdown penuh atau hibernasi kernel tergantung apakah fast startup menyala, dan Restart selalu melakukan boot penuhFast startup menyalaHibernate mati / ServerShut downRestartSesi berakhir + hibernasi kernelShutdown penuhBerikutnya: pulihkan kernelBerikutnya: boot penuh

Gambar 2: Operasi Shut down belah menjadi shutdown penuh atau hibernasi kernel tergantung apakah fast startup menyala, dan Restart selalu melakukan boot penuh.

“Restart”, sebaliknya, selalu menjalankan siklus boot lengkap. Setelah pembaruan driver, misalnya, Anda membutuhkan state yang sama sekali baru.2 Dari itu, beberapa fenomena yang Anda dengar di lapangan jatuh ke tempatnya.

  • “Saya mematikannya dan menyalakannya lagi, tetapi masalah perangkat tidak hilang” — kernel dan driver hanya dipulihkan dari hibernasi; mereka tidak di-reset
  • “Ia membaik setelah saya restart” — karena boot penuh menginisialisasinya
  • Prosedur insiden PC perangkat harus mengatakan “Restart”, bukan “matikan dan nyalakan”

Jika Anda ingin membuat shutdown penuh eksplisit dari baris perintah, shutdown /s (bawaan Shutdown.exe adalah shutdown penuh); jika Anda ingin perilaku hybrid bawaan, shutdown /s /hybrid.2 Menonaktifkan fast startup tidak direkomendasikan. Sisi aplikasi harus mengasumsikan “pada shutdown kernel mungkin hanya berhibernasi” — misalnya, jangan memperkirakan “uptime kumulatif” dari waktu boot OS — dan merancang agar tidak rusak dengan cara mana pun (apakah fast startup menyala atau mati berbeda menurut lingkungan).

3. Bagaimana aplikasi GUI harus berperilaku — WM_QUERYENDSESSION dan WM_ENDSESSION

3.1. Bagaimana dua pesan membagi pekerjaan

Aplikasi yang punya jendela dan antrean pesan diberitahu tentang akhir sesi dalam dua tahap.1

  1. WM_QUERYENDSESSION — kueri: “apakah tidak apa-apa untuk berakhir?” Aplikasi harus mengembalikan TRUE segera; respons bawaan DefWindowProc juga TRUE. Jangan mulai pembersihan di sini.
  2. WM_ENDSESSION (wParam=TRUE) — notifikasi yang dikomit: “sesi benar-benar berakhir”. Pembersihan terjadi di sini.

Mengembalikan FALSE ke WM_QUERYENDSESSION dapat membatalkan shutdown, tetapi dokumentasi eksplisit bahwa “Anda harus mengembalikan TRUE dan menghormati niat pengguna”, dan aplikasi yang mengembalikan FALSE tetap diekspos di UI layar penuh sebagai “aplikasi yang mencegah shutdown”. Aplikasi konsol dan aplikasi tanpa jendela terlihat tidak dapat membatalkan shutdown sejak awal, dan jika mereka tidak merespons dalam 5 detik mereka diakhiri secara otomatis.14

Alur notifikasi akhir sesi dua tahapMengembalikan TRUE ke kueri WM_QUERYENDSESSION mengomit dengan WM_ENDSESSION dan pembersihan berjalan. Menolak dengan FALSE menampilkan aplikasi sebagai yang mencegah shutdown, dan sekitar 5 detik tanpa respons dapat memaksa lanjutTRUE(aturan)FALSE(tolak)Tanpa balasan ~5 dtkPaksa lanjutBatalWM_QUERYENDSESSIONWM_ENDSESSION(dikomit)Ditampilkan sebagai memblokir shutdownDiperlakukan sebagai hangPembersihan di siniProses keluarShutdown dibatalkan

Gambar 3: Mengembalikan TRUE ke kueri WM_QUERYENDSESSION mengomit dengan WM_ENDSESSION dan pembersihan berjalan. Menolak dengan FALSE menampilkan aplikasi sebagai yang mencegah shutdown, dan sekitar 5 detik tanpa respons dapat memaksa lanjut.

3.2. Apa yang terjadi jika Anda tidak merespons — dinding 5 detik

Pada WM_QUERYENDSESSION dan WM_ENDSESSION, Anda dapat menunda respons sekitar 5 detik. Melewati itu, sistem menampilkan layar “This app is preventing shutdown”, dan pengguna dapat memilih paksa lanjut (= paksa-akhiri aplikasi).4 Proses yang diakhiri paksa tidak diberi kesempatan lain untuk menyelesaikan simpannya.

Poin desain karenanya dua ini.

  • Jaga pembersihan pada jumlah yang selesai dalam 5 detik. Microsoft sendiri merekomendasikan menyimpan data sering dalam operasi biasa agar lebih sedikit yang harus disimpan saat shutdown, dan menyimpan data yang belum disimpan ke lokasi sementara untuk dipulihkan pada peluncuran berikutnya.1
  • Jangan menaruh dialog konfirmasi selama shutdown. Sementara Anda duduk menunggu “Do you want to save?”, 5 detik lewat. Diam-diam jatuh ke sisi aman (autosave).

3.3. Implementasi di WinForms dan WPF

Dalam aplikasi desktop .NET, pesan-pesan ini diterjemahkan menjadi event kerangka. Di WinForms, FormClosing dinaikkan, dan CloseReason memberi tahu Anda apakah shutdown penyebabnya.

// 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
}

Di WPF, event Application.SessionEnding (atribut XAML SessionEnding, atau override OnSessionEnding) bersesuaian.

Bagaimana event WinForms/WPF memetakan ke pesanFase kueri WM_QUERYENDSESSION memetakan ke WinForms FormClosing dan WPF SessionEnding, dan yang Anda lakukan di sana paling banyak adalah simpan snapshot idempoten. Tidak ada event yang bersesuaian untuk WM_ENDSESSION yang dikomit, jadi terima di WndProc atau hook dan lakukan pembersihan yang hanya dapat berjalan setelah komitWM_QUERYENDSESSIONWinForms: FormClosingWPF: SessionEndingHanya snapshot idempotenWM_ENDSESSIONTidak ada event: hook WndProcPembersihan setelah komit

Gambar 4: Fase kueri WM_QUERYENDSESSION memetakan ke WinForms FormClosing dan WPF SessionEnding, dan yang Anda lakukan di sana paling banyak adalah simpan snapshot idempoten. Tidak ada event yang bersesuaian untuk WM_ENDSESSION yang dikomit, jadi terima di WndProc atau hook dan lakukan pembersihan yang hanya dapat berjalan setelah komit.

// 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
}

Ada satu peringatan di sini. Baik FormClosing (CloseReason.WindowsShutDown) maupun SessionEnding WPF bersesuaian dengan fase kueri (WM_QUERYENDSESSION). Jika aplikasi lain menolak, shutdown dibatalkan dan aplikasi Anda tetap berjalan. Karena itu yang boleh Anda lakukan di event ini adalah simpan snapshot idempoten yang tidak merugikan jika shutdown dibatalkan dan menghasilkan hasil yang sama tidak peduli berapa kali dijalankan. Jika Anda membutuhkan “pembersihan yang harus dilakukan hanya ketika kita benar-benar berakhir” (memutus, mengembalikan sumber daya, dan sebagainya), hook WM_ENDSESSION yang dikomit (wParam=TRUE) secara langsung di WndProc dan lakukan di sana.

Pada jalur mana pun, lipat tubuh ke fungsi “simpan snapshot” bersama dan tulis data pulih untuk keluar normal, shutdown, dan (jika mungkin) crash dalam format yang sama, agar logika pulih pada peluncuran berikutnya adalah satu jalur. Merancang untuk meninggalkan informasi bahkan pada crash dibahas di “Merancang aplikasi Windows agar meninggalkan log dan dump ketika mereka crash”.

4. Jika Anda sungguh harus memblokir — ShutdownBlockReasonCreate

Operasi yang secara fisik rusak jika dipotong di tengah, seperti menulis CD atau firmware, adalah pengecualian. Praktik yang benar di sini adalah mendaftarkan string alasan dengan ShutdownBlockReasonCreate ketika operasi yang tidak dapat diinterupsi dimulai, dan memanggil ShutdownBlockReasonDestroy segera ketika ia selesai. Ketika shutdown diminta, alasan itu ditampilkan di layar “This app is preventing shutdown”, dan pengguna dapat memutuskan apakah lanjut atau batal.3

Alur perlindungan dengan ShutdownBlockReasonCreateDaftarkan alasan ketika operasi yang tidak dapat diinterupsi dimulai; jika permintaan shutdown tiba saat dilindungi, alasan ditampilkan layar penuh dan WM_QUERYENDSESSION ditolak dengan FALSE. Pengguna dapat batal atau paksa lanjut, dan alasan dikosongkan ketika operasi selesaiBatalPaksa lanjutMulai pekerjaan yang tidak dapat diinterupsiShutdownBlockReasonCreateJalankan pada thread pekerjaSelesai: DestroyShutdown selama iniTampilkan alasan + FALSEProses keluar

Gambar 5: Daftarkan alasan ketika operasi yang tidak dapat diinterupsi dimulai; jika permintaan shutdown tiba saat dilindungi, alasan ditampilkan layar penuh dan WM_QUERYENDSESSION ditolak dengan FALSE. Pengguna dapat batal atau paksa lanjut, dan alasan dikosongkan ketika operasi selesai.

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

Kesalahpahaman yang mudah di sini adalah pembagian peran. Semua yang dilakukan ShutdownBlockReasonCreate adalah mendaftarkan string alasan; ia sendiri tidak menghentikan shutdown. Yang sebenarnya menahan shutdown adalah penanganan Anda sendiri yang mengembalikan FALSE ke WM_QUERYENDSESSION sementara flag perlindungan disetel, seperti di atas. Pakai keduanya sebagai set, dan kosongkan keduanya segera ketika operasi selesai. Juga, jalankan operasi yang dilindungi itu sendiri pada thread pekerja dan jaga thread UI mampu memproses pesan — mekanisme penolakan hanya bekerja setelah pesan tiba (dan bahkan lalu pengguna dan OS dapat memaksa lanjut, jadi desain tulis yang tidak rusak “jika ia tidak berhenti” — Bab 8 — tetap diperlukan).

Ada tiga peringatan operasional.

  • Jaga string alasan pendek dan spesifik. Pengguna sedang terburu-buru dan hanya akan membaca beberapa detik. Dokumentasi sendiri memberi “Burning a CD” sebagai contoh yang sesuai.3
  • Jangan biarkan ia terdaftar sepanjang hidup aplikasi. “Hanya selama operasi yang tidak dapat diinterupsi sedang berlangsung” adalah yang diasumsikan API.
  • Jangan merancang dengan asumsi bahwa Anda dapat memblokir. Pengguna dapat memilih paksa lanjut, dan shutdown paksa (ENDSESSION_CRITICAL) tidak akan menunggu sejak awal. Dokumentasi eksplisit: “Applications should not depend on being able to block shutdown”.4

5. Bagaimana aplikasi konsol dan proses latar belakang harus berperilaku

5.1. SetConsoleCtrlHandler dan masa tenggang pendek

Aplikasi konsol tidak dapat menerima pesan jendela, jadi sinyal kontrol tiba di fungsi handler yang didaftarkan dengan SetConsoleCtrlHandler. Masa tenggang bawaan per sinyal adalah sebagai berikut.5

Sinyal Kapan terjadi Masa tenggang bawaan
CTRL_C_EVENT / CTRL_BREAK_EVENT Ctrl+C / Ctrl+Break Tidak ada timeout
CTRL_CLOSE_EVENT Menutup konsol, “End task” Task Manager (pembunuhan proses paksa dari tab “Details” adalah keluar segera tanpa notifikasi, dan di luar tabel ini) Sekitar 5 detik
CTRL_SHUTDOWN_EVENT Shutdown sistem (proses layanan) Sekitar 20 detik

Ada dua poin untuk diwaspadai. Pertama, pada dasarnya hanya proses yang berjalan sebagai layanan yang dapat menerima CTRL_LOGOFF_EVENT dan CTRL_SHUTDOWN_EVENT. Aplikasi dalam sesi interaktif diakhiri saat keluar, jadi desain yang menunggu sinyal ini tidak bertahan.5 Kedua, proses yang telah memuat gdi32.dll atau user32.dll diperlakukan sebagai aplikasi Windows bahkan jika Anda menganggapnya aplikasi konsol, dan handler LOGOFF/SHUTDOWN tidak dipanggil. Solusi resmi adalah membuat jendela tersembunyi dan menerima WM_QUERYENDSESSION/WM_ENDSESSION.6

Masa tenggang per sinyal konsolCtrl+C dan Ctrl+Break tidak punya timeout eksplisit; tutup konsol punya sekitar 5 detik dan sinyal shutdown ke proses layanan punya sekitar 20 detik; melebihi itu memaksa-akhiri prosesTanpa timeoutSekitar 5 dtkSekitar 20 dtkCTRL_C / BREAKPembersihan HandlerRoutineCTRL_CLOSECTRL_SHUTDOWNPaksa-bunuh setelah tenggang

Gambar 6: Ctrl+C dan Ctrl+Break tidak punya timeout eksplisit; tutup konsol punya sekitar 5 detik dan sinyal shutdown ke proses layanan punya sekitar 20 detik; melebihi itu memaksa-akhiri proses.

// 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. Jebakan .NET — jangan mengandalkan ProcessExit

Di .NET sudah lama ada pola stok “cukup bersihkan di AppDomain.ProcessExit”, tetapi dari .NET 10 runtime tidak lagi menyediakan handler sinyal terminasi bawaan, dan baik ProcessExit maupun AssemblyLoadContext.Unloading tidak menyala pada CTRL_CLOSE_EVENT/CTRL_SHUTDOWN_EVENT. Handler bawaan OS cukup mengakhiri proses segera.7

Bagaimana ProcessExit berubah di .NET 10Sampai .NET 9 handler sinyal bawaan runtime menerima sinyal terminasi, menaikkan ProcessExit, lalu keluar. Dari .NET 10 runtime tidak menyediakan handler bawaan, penanganan bawaan OS mengakhiri proses segera, dan Anda mendaftarkan handler sendiriCTRL_CLOSE / SHUTDOWNSampai .NET 9: ProcessExitDari .NET 10: keluar segeraDaftarkan handler sendiri

Gambar 7: Sampai .NET 9 handler sinyal bawaan runtime menerima sinyal terminasi, menaikkan ProcessExit, lalu keluar. Dari .NET 10 runtime tidak menyediakan handler bawaan, penanganan bawaan OS mengakhiri proses segera, dan Anda mendaftarkan handler sendiri.

Sebaliknya, pindah ke jalur kanonis untuk setiap model aplikasi.

  • Aplikasi GUI: FormClosing / SessionEnding dari bab sebelumnya
  • Generic Host (termasuk Worker Service): IHostApplicationLifetime dan BackgroundService.StopAsync. Buat masa tenggang berhenti eksplisit dengan HostOptions.ShutdownTimeout
  • Aplikasi konsol telanjang: SetConsoleCtrlHandler (atau berlangganan padanan SIGINT/SIGTERM dengan PosixSignalRegistration)
Di mana setiap model aplikasi menerima notifikasi keluarAplikasi GUI memakai FormClosing dan SessionEnding plus hook WM_ENDSESSION untuk pekerjaan yang dikomit; Generic Host memakai IHostApplicationLifetime dan StopAsync; aplikasi konsol telanjang memakai SetConsoleCtrlHandler atau PosixSignalRegistration. Mengandalkan ProcessExit tidak menyala pada jalur sinyal eksternalGUIBukan GUIHostKonsolModel aplikasi mana?FormClosing / SessionEndingHost atau konsol?Hook ENDSESSIONLifetime + StopAsyncSetConsoleCtrlHandlerSetel ShutdownTimeoutJangan andalkan ProcessExit

Gambar 8: Aplikasi GUI memakai FormClosing dan SessionEnding plus hook WM_ENDSESSION untuk pekerjaan yang dikomit; Generic Host memakai IHostApplicationLifetime dan StopAsync; aplikasi konsol telanjang memakai SetConsoleCtrlHandler atau PosixSignalRegistration. Mengandalkan ProcessExit tidak menyala pada jalur sinyal eksternal.

Masa tenggang berbeda menurut jalur — sekitar 5 detik untuk GUI dan tutup konsol, masa tenggang SCM Bab 6 untuk layanan (sekitar 20 detik, atau nilai yang dikonfigurasi untuk PRESHUTDOWN), dan tidak ada timeout eksplisit untuk Ctrl+C. Pada setiap jalur, walaupun, masa tenggang terbatas dan tidak dapat diandalkan, jadi sumbu desain adalah bahwa kasus normal adalah “sudah disimpan pada setiap titik pemeriksaan pemrosesan”, bukan “bekerja keras di event keluar”.

6. Bagaimana layanan Windows harus berperilaku — SHUTDOWN dan PRESHUTDOWN

6.1. Dua jenis notifikasi shutdown

Layanan tidak terpengaruh oleh keluar, tetapi dihentikan pada shutdown dan restart. Notifikasi tiba sebagai kode kontrol dari Service Control Manager (SCM), dan menerimanya mensyaratkan menyatakan flag penerimaan.8

Pernyataan Notifikasi yang tiba Waktu dan masa tenggang
SERVICE_ACCEPT_SHUTDOWN SERVICE_CONTROL_SHUTDOWN Diberitahu selama pemrosesan shutdown. Bawaan sekitar 20 detik, batas atas WaitToKillServiceTimeout
SERVICE_ACCEPT_PRESHUTDOWN SERVICE_CONTROL_PRESHUTDOWN Diberitahu sebelum SHUTDOWN. SCM menunggu sampai layanan berhenti atau timeout
Urutan notifikasi shutdown ke layananKetika shutdown dimulai, layanan yang menyatakan PRESHUTDOWN diberitahu lebih dulu dengan masa tenggang yang dikonfigurasi, lalu notifikasi SHUTDOWN dikirim dengan bawaan sekitar 20 detik, dan proses diakhiri ketika masa tenggang habisShutdown dimulaiPRESHUTDOWN(jika dinyatakan)SHUTDOWN(sekitar 20 dtk)Tenggang habis → keluar

Gambar 9: Ketika shutdown dimulai, layanan yang menyatakan PRESHUTDOWN diberitahu lebih dulu dengan masa tenggang yang dikonfigurasi, lalu notifikasi SHUTDOWN dikirim dengan bawaan sekitar 20 detik, dan proses diakhiri ketika masa tenggang habis.

Timeout PRESHUTDOWN dapat dikonfigurasi dengan ChangeServiceConfig2 (SERVICE_CONFIG_PRESHUTDOWN_INFO); bawaan adalah 10 detik dari Windows 10 Creators Update (build 15063) ke atas, dan 3 menit sebelumnya.9 Jika Anda masih bekerja dari pengetahuan lama bahwa “PRESHUTDOWN memberi Anda 3 menit”, pada OS saat ini Anda hanya punya 1/18 masa tenggang yang Anda harapkan. Juga, PRESHUTDOWN menahan shutdown seluruh sistem selama interval itu, jadi dokumentasi juga mengatakan ia “should be used only in special circumstances”.8

Praktik sisi handler juga penting. Handler kontrol harus kembali dalam 30 detik; serahkan pekerjaan berhenti yang memakan waktu ke thread lain, laporkan SERVICE_STOP_PENDING, dan kembali segera.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. Desain yang tidak mengandalkan masa tenggang

Memperpanjang plafon masa tenggang, WaitToKillServiceTimeout, dengan menulisnya ulang dari sisi layanan secara eksplisit tidak direkomendasikan. Dokumentasi meminta yang sebaliknya — layanan harus menyelesaikan pembersihan secepat mungkin agar mesin bertenaga UPS dapat menyelesaikan shutdown sebelum baterai habis. Panduannya adalah menyimpan sering dalam operasi biasa agar data yang belum disimpan diminimalkan, tidak menghabiskan waktu membebaskan memori saat shutdown, dan tidak menunggu terlalu lama untuk balasan ketika memberitahu peer jaringan. Juga, SCM saat shutdown secara bawaan tidak mempertimbangkan dependensi, jadi pemrosesan berhenti tetap harus bekerja “bahkan jika layanan yang Anda andalkan sudah turun”.8

Merancang pemrosesan berhenti yang tidak mengandalkan masa tenggangJika Anda menyimpan pada setiap titik pemeriksaan pemrosesan agar data yang belum disimpan selalu minimal, pembersihan ketika notifikasi berhenti tiba selesai dalam beberapa detik. Desain yang menyimpan semuanya saat keluar tidak akan muat di masa tenggang, dan pengakhiran paksa kehilangan dataSimpan pada setiap titik pemeriksaanBerhenti → simpan sliver → selesaiSimpan semuanya saat keluarBerhenti → simpan melewatkan tenggangPaksa-bunuh → kehilangan data

Gambar 10: Jika Anda menyimpan pada setiap titik pemeriksaan pemrosesan agar data yang belum disimpan selalu minimal, pembersihan ketika notifikasi berhenti tiba selesai dalam beberapa detik. Desain yang menyimpan semuanya saat keluar tidak akan muat di masa tenggang, dan pengakhiran paksa kehilangan data.

Dalam .NET Worker Service (UseWindowsService), SERVICE_CONTROL_STOP dan SHUTDOWN diterjemahkan menjadi host stop, dan BackgroundService.StopAsync dipanggil. Implementasi stok pada tulisan ini menerima keluarga STOP/SHUTDOWN; jika Anda juga membutuhkan PRESHUTDOWN Anda akan membutuhkan handler yang diperluas. Bagaimanapun, buat HostOptions.ShutdownTimeout eksplisit dan selesaikan StopAsync dalam beberapa detik. Untuk membangun layanan secara umum, lihat “Cara membangun dan mengoperasikan layanan Windows”.

7. Pulih secara otomatis setelah restart

Pada PC perangkat atau PC tanpa pengawasan, cakupan desain bukan hanya “bertahan dari shutdown” melainkan “kembali sendiri setelah restart”.

7.1. RegisterApplicationRestart dan callback pemulihan

Jika Anda telah memanggil RegisterApplicationRestart, aplikasi didaftarkan sebagai kandidat restart untuk crash (pengecualian yang tidak ditangani), tidak merespons, restart aplikasi yang didorong pembaruan, dan restart OS yang didorong pembaruan. Anda dapat mendaftarkan argumen baris perintah untuk restart, jadi jika Anda menyertakan “berkas mana yang terbuka” dan “titik pulih mana”, Anda dapat melanjutkan dari tempat Anda berhenti setelah restart.10

Spesifikasi yang harus diserap adalah sebagai berikut.10

  • Pendaftaran harus selesai sebelum masalah terjadi (selama penanganan WM_QUERYENDSESSION adalah kesempatan terakhir dalam skenario pembaruan)
  • Untuk mencegah loop restart, proses yang telah berjalan kurang dari 60 detik tidak di-restart
  • Proses yang berjalan elevated bukan kandidat restart otomatis (proses tidak dapat dibuat ulang tanpa persetujuan elevasi). Pemulihan otomatis aplikasi yang membutuhkan elevasi dirancang dengan menjaga UI pada hak standar dan mengisolasi pekerjaan berhak istimewa di layanan, atau lewat jalur peluncuran eksplisit seperti tugas Task Scheduler “Run with highest privileges”
  • Restart setelah crash atau hang melalui persetujuan pengguna; restart setelah pembaruan otomatis
  • Untuk pulih melewati restart OS, sisi yang meminta restart (penginstal dan semacamnya) harus memanggil API shutdown dengan flag EWX_RESTARTAPPS / SHUTDOWN_RESTARTAPPS

Jika Anda juga mendaftarkan RegisterApplicationRecoveryCallback, WER (Windows Error Reporting) memanggil callback pada crash dan memberi Anda masa tenggang untuk menyimpan data yang sedang berlangsung. Jika simpan butuh waktu, walaupun, Anda harus terus memanggil ApplicationRecoveryInProgress dalam interval ping yang ditentukan saat pendaftaran atau pekerjaan pemulihan dipotong di tengah. Ketika simpan selesai, beritahu penyelesaian dengan ApplicationRecoveryFinished. “Mengganti berkas yang sedang dipakai dan me-restart” pada waktu pembaruan aplikasi adalah wilayah Restart Manager, dibahas secara rinci di “Cara mengganti exe atau DLL yang sedang dipakai”.

7.2. ARSO — sign-in otomatis setelah restart pembaruan

Setelah restart Windows Update, jika tidak ada yang masuk, aplikasi sesi pengguna tidak kembali. Yang mengisi celah itu adalah ARSO (Winlogon Automatic Restart Sign-On). Ketika Windows Update memulai restart, ia menyimpan kredensial pengguna interaktif terakhir secara aman, mengonfigurasi Autologon, dan setelah restart secara otomatis memasukkan pengguna itu lalu mengunci layar.11 Ada juga perintah seperti shutdown /g yang meminta restart plus lanjut aplikasi yang terdaftar. Beberapa lingkungan menonaktifkan ini dengan kebijakan organisasi (DisableAutomaticRestartSignOn dan semacamnya), jadi ketika Anda merancang pemulihan tanpa pengawasan, periksa pengaturan ini sebagai set. Dan jika Anda mengandalkan peluncuran otomatis dalam sesi pengguna untuk pekerjaan latar belakang yang selalu Anda butuhkan, langkah yang benar adalah menjadikannya layanan Windows sejak awal.

Jalur di mana aplikasi pulih secara otomatis setelah restartJika Anda mendaftar dengan RegisterApplicationRestart sebelum masalah terjadi, aplikasi di-restart setelah persetujuan pengguna pada crash atau tidak merespons, dan setelah sign-in otomatis ARSO serta kunci layar pada restart yang didorong pembaruan. Proses di bawah 60 detik runtime dan proses elevated di luar cakupanPersetujuanRegisterApplicationRestartCrash atau hangRestart pembaruanRestart aplikasiSign-in ARSO + kunciBukan: di bawah 60 dtk / elevated

Gambar 11: Jika Anda mendaftar dengan RegisterApplicationRestart sebelum masalah terjadi, aplikasi di-restart setelah persetujuan pengguna pada crash atau tidak merespons, dan setelah sign-in otomatis ARSO serta kunci layar pada restart yang didorong pembaruan. Proses di bawah 60 detik runtime dan proses elevated di luar cakupan.

8. Bertahan dari kehilangan daya yang tidak membawa notifikasi — desain tulis dan UPS

8.1. Tulis yang “tidak rusak tidak peduli kapan dipotong” — berkas sementara + ReplaceFile

Pemutus yang trip, PSU yang gagal, atau colokan yang ditarik tidak membawa WM_ENDSESSION maupun PRESHUTDOWN. Selama Anda “menimpa berkas asli di tempat” untuk pengaturan atau hasil pengukuran, kehilangan daya di tengah tulis dapat meninggalkan berkas rusak yang mencampur lama dan baru.

Pola standar adalah menulis lengkap ke berkas sementara pada volume yang sama lalu tukar. ReplaceFile mengemas urutan “simpan ke berkas baru → sisihkan yang asli → ganti nama → hapus” ke dalam satu API, dan juga membawa atribut berkas asli seperti waktu pembuatan, ACL, dan stream alternatif (ketiga berkas harus pada volume yang sama).12 File.Replace milik .NET memanggil ini apa adanya.

Alur simpan dan pulih dengan berkas sementara dan ReplaceFileSaat simpan, tulis lengkap ke berkas sementara, flush, dan tukar dengan ReplaceFile, meninggalkan isi lama di .bak. Pada peluncuran berikutnya, validasi berkas primer dan jatuh ke .bak jika rusakPada peluncuran berikutnyaSaat simpanUtuhRusakKehilangan daya pada langkah mana punValidasi primerPakai apa adanyaJatuh ke .bakFlush ke diskTulis berkas temp lengkapReplaceFile → .bak

Gambar 12: Saat simpan, tulis lengkap ke berkas sementara, flush, dan tukar dengan ReplaceFile, meninggalkan isi lama di .bak. Pada peluncuran berikutnya, validasi berkas primer dan jatuh ke .bak jika rusak.

// 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;
    }
}

Dengan ini, operasi biasa selalu meninggalkan Anda mampu membaca baik “berkas lama yang lengkap” maupun “berkas baru yang lengkap”. ReplaceFile, walaupun, adalah operasi namespace multi-langkah, dan atomisitas melewati kehilangan daya tidak dijamin oleh spesifikasi. Itulah mengapa contoh di atas menyimpan cadangan (.bak) — sisi baca memvalidasi berkas primer saat peluncuran dan jatuh ke cadangan jika rusak, sebagai set. Anda tidak dapat memakai ini untuk log append-only atau CSV, jadi itu memakai format yang membangun kerusakan ke dalamnya, seperti “satu baris = satu rekaman, dan buang baris terakhir yang rusak saat baca”.

8.2. Sukses dari WriteFile bukan kedatangan di disk

Premis lain adalah bahwa bahkan jika WriteFile mengembalikan sukses, data mungkin masih hanya di cache OS. Windows menaruh baca dan tulis berkas pada buffer sistem dan mencerminkannya ke disk secara berkala dengan lazy writing. Untuk membawa data ke disk secara pasti, flush secara eksplisit dengan FlushFileBuffers, atau tentukan FILE_FLAG_WRITE_THROUGH di CreateFile agar setiap tulis melalui cache. Metadata sistem berkas selalu di-cache, jadi mengonfirmasi metadata juga membutuhkan flush atau write-through.13

Memanggil FlushFileBuffers setiap kali tidak efisien, walaupun, dan dokumentasi juga mendorong mempertimbangkan FILE_FLAG_NO_BUFFERING+WRITE_THROUGH alih-alih panggilan sering.13 Dalam praktik, “flush hanya pada titik pemeriksaan transaksi atau tepat sebelum menutup berkas” adalah kompromi yang realistis. Mekanika lapisan ini — cache manager, lazy writing, dan fakta bahwa “saya flush dan ia masih mungkin belum mencapai disk” karena cache perangkat keras — dibahas secara mendalam di “Cache Manager: kapan WriteFile Anda benar-benar mencapai disk?”.

8.3. UPS dan pemantauan baterai — mengubah kehilangan daya menjadi shutdown

Penanggulangan sungguhan terhadap kehilangan daya pada PC perangkat adalah UPS. Pikirkan peran UPS bukan sebagai “menghentikan pemadaman” melainkan sebagai mengubah “kehilangan daya tanpa notifikasi” menjadi “shutdown terencana dengan notifikasi”. Desainnya adalah setup dua tahap.

  1. Merancang masa tenggang: waktu tahan baterai UPS > jumlah “deteksi peralihan ke baterai → pembersihan aplikasi dan layanan → shutdown OS selesai”. Jika pemrosesan berhenti layanan lambat, persamaan ini tidak lagi bertahan (Bagian 6.2)
  2. Deteksi: Peralihan dari AC ke baterai, dan penurunan kapasitas tersisa, diberitahu dengan event PBT_APMPOWERSTATUSCHANGE. Aplikasi dengan jendela menerimanya sebagai WM_POWERBROADCAST; layanan tanpa jendela menyatakan SERVICE_ACCEPT_POWEREVENT dan menerimanya sebagai SERVICE_CONTROL_POWEREVENT di HandlerEx (WM_POWERBROADCAST tidak tiba di handler kontrol layanan). Saat penerimaan, panggil GetSystemPowerStatus, periksa ACLineStatus (apakah pada AC) dan BatteryLifePercent, dan antar ke menginterupsi pengukuran, menyimpan, dan meminta shutdown15
Alur mengubah kehilangan daya menjadi shutdown terencana dengan UPSKetika pemadaman mengalihkan UPS ke baterai, PBT_APMPOWERSTATUSCHANGE diberitahu, status daya diperiksa, dan simpan plus permintaan shutdown mengubah kehilangan daya tanpa notifikasi menjadi shutdown terencana dengan notifikasiPemadamanUPS ke bateraiPBT_APMPOWERSTATUSCHANGEGetSystemPowerStatusInterupsi dan simpanMinta shutdownAlur notifikasi biasa(3–6)

Gambar 13: Ketika pemadaman mengalihkan UPS ke baterai, PBT_APMPOWERSTATUSCHANGE diberitahu, status daya diperiksa, dan simpan plus permintaan shutdown mengubah kehilangan daya tanpa notifikasi menjadi shutdown terencana dengan notifikasi.

UPS tipikal yang terhubung USB muncul ke Windows sebagai baterai, jadi Anda dapat mendeteksinya dengan API standar ini. Jika perangkat lunak manajemen vendor punya fitur “matikan OS pada N% tersisa”, juga periksa bahwa ambang selaras dengan waktu pembersihan aplikasi Anda. Lanjut dari tidur atau hibernasi, dan masalah berjalan lama, adalah sumbu terpisah, dibahas di “Tidur, hibernasi, Modern Standby, dan aplikasi yang berjalan lama”.

9. Cara memverifikasi — mencoba shutdown dengan aman

Penanganan shutdown cenderung menjadi “kita menulisnya tetapi tidak pernah mencobanya dalam kondisi setara produksi”. Jaga prosedur untuk memverifikasinya dengan aman.

  • Coba pada mesin uji atau VM: Jangan coba lebih dulu pada PC perangkat produksi. Di lingkungan uji dengan checkpoint Hyper-V (snapshot), ulangi shutdown, restart, dan kehilangan daya paksa (mematikan VM). “Power off” VM, walaupun, hanya mereproduksi “OS tamu berhenti tanpa pemberitahuan”; ia tidak mereproduksi hilangnya cache volatil disk fisik atau kerusakan yang bergantung pengontrol. Jika Anda mengirimkannya sebagai PC perangkat, pemeriksaan terakhir adalah uji potong daya sungguhan pada perangkat keras setara produksi
  • Pemeriksaan cepat dengan keluar: Jalur WM_QUERYENDSESSION → WM_ENDSESSION juga berjalan pada keluar (satu-satunya perbedaan adalah bit ENDSESSION_LOGOFF disetel di lParam), jadi Anda dapat dengan nyaman mengonfirmasi perilaku kode pembersihan pada mesin pengembangan1
  • Coba shutdown penuh dan hybrid secara terpisah: Coba shutdown /s /t 0 (penuh), shutdown /s /hybrid /t 0 (perilaku bawaan), dan shutdown /r /t 0 (restart) masing-masing2
  • Ukur berapa lama pembersihan butuh: Tulis stempel waktu ke log di awal dan akhir fungsi pembersihan, dan ukur apakah ia muat dalam 5 detik (atau masa tenggang yang dikonfigurasi untuk layanan)
Operasi untuk memverifikasi dan apa yang masing-masing dapat konfirmasiKeluar adalah pemeriksaan nyaman jalur notifikasi; shutdown-perintah penuh, hybrid, dan restart mengonfirmasi jalur notifikasi produksi dan masa tenggang; power-off VM menguji ketahanan berhenti-mendadak; uji potong daya fisik adalah pemeriksaan terakhir termasuk penyimpanan fisikKeluarJalur QUERY → ENDSESSIONshutdown /s /hybrid /rJalur produksi + tenggangPower-off VMBerhenti tamu mendadakPotong daya fisikTermasuk penyimpanan(akhir)

Gambar 14: Keluar adalah pemeriksaan nyaman jalur notifikasi; shutdown-perintah penuh, hybrid, dan restart mengonfirmasi jalur notifikasi produksi dan masa tenggang; power-off VM menguji ketahanan berhenti-mendadak; uji potong daya fisik adalah pemeriksaan terakhir termasuk penyimpanan fisik.

Untuk isolasi setelah-fakta, event log (System) berguna. Pada shutdown atau restart normal, event ID 1074 (proses mana memulai shutdown, untuk siapa, dan untuk alasan apa) dicatat. Pada kehilangan daya atau crash mendadak tidak ada 1074, dan pada boot berikutnya event ID 41 (Kernel-Power) dan 6008 (The previous system shutdown was unexpected) dicatat.14 “Apa yang terjadi semalam” dimulai di sini.

# 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

Jika 1074 menunjukkan “restart oleh Windows Update” dan data aplikasi rusak, masalahnya adalah kode pembersihan. 6008/41 hanya menunjukkan “shutdown tak terduga”; mereka juga dicatat untuk layar biru (crash) atau reset paksa, bukan hanya kehilangan daya. Jika BugcheckCode 41 bukan nol itu crash; jika 0 dan tidak ada memory dump juga, kehilangan daya mungkin — isolasi penyebab dari informasi sekitar, dan setelah Anda tahu itu kehilangan daya, desain tulis Bab 8 dan UPS adalah langkah berikutnya.

10. Ringkasan

  • Shutdown adalah “event normal yang akan datang, cepat atau lambat”. Masa tenggang setelah notifikasi, pada prinsipnya, hanya sekitar 5 detik, jadi prasyaratnya adalah autosave sering agar “apa yang Anda lakukan saat keluar” diminimalkan.
  • Pada OS klien dari Windows 8 ke atas, jika fast startup diaktifkan, “Shut down” adalah hybrid shutdown dan kernel hanya berhibernasi. Satu-satunya reset penuh adalah “Restart” — tulis “Restart” ke dalam prosedur insiden.
  • Aplikasi GUI mengembalikan TRUE segera ke WM_QUERYENDSESSION, dan melakukan pembersihan yang dikomit di WM_ENDSESSION. FormClosing dan SessionEnding WinForms/WPF bersesuaian dengan fase kueri, jadi yang Anda lakukan di sana paling banyak adalah simpan snapshot idempoten. Jangan menaruh dialog selama shutdown.
  • Operasi yang sungguh tidak dapat diinterupsi dilindungi dengan menampilkan alasan dengan ShutdownBlockReasonCreate. Tidak ada, walaupun, jaminan di mana pun bahwa Anda dapat memblokir.
  • Aplikasi konsol menerima notifikasi dengan SetConsoleCtrlHandler; layanan dengan SERVICE_ACCEPT_PRESHUTDOWN/SHUTDOWN. Masa tenggang PRESHUTDOWN bawaan adalah 10 detik pada OS saat ini. Di .NET, berhenti mengandalkan ProcessExit dan pindah ke jalur kanonis untuk model aplikasi.
  • Pemulihan setelah restart dapat tanpa pengawasan dengan RegisterApplicationRestart (+ callback pemulihan) dan ARSO.
  • Kehilangan daya tidak membawa notifikasi. Bersiaplah dengan tukar berkas-sementara + ReplaceFile (sebagai set dengan cadangan + validasi saat muat), flush pada titik pemeriksaan, dan UPS yang “mengubah kehilangan daya menjadi shutdown terencana”.
Gambar keseluruhan penanganan shutdownUntuk akhir dengan notifikasi, respons dengan pembersihan yang dapat menutup toko dalam beberapa detik dan antar ke pemulihan otomatis setelah restart; untuk kehilangan daya tanpa notifikasi, bersiaplah dengan tulis yang tidak rusak tidak peduli kapan dipotong dan UPS, dan verifikasi termasuk pada perangkat keras fisik. Dua pilar ini adalah kesimpulan artikelDengan notifikasiTanpa notifikasiBagaimana ia berakhirPembersihan beberapa detik(3–6)Tulis aman + UPS(8)Pulih otomatis setelah restart(7)Verifikasi pada perangkat keras(9)

Gambar 15: Untuk akhir dengan notifikasi, respons dengan pembersihan yang dapat menutup toko dalam beberapa detik dan antar ke pemulihan otomatis setelah restart; untuk kehilangan daya tanpa notifikasi, bersiaplah dengan tulis yang tidak rusak tidak peduli kapan dipotong dan UPS, dan verifikasi termasuk pada perangkat keras fisik. Dua pilar ini adalah kesimpulan artikel.

  • Verifikasi dengan aman pada VM dan dengan keluar, dan isolasi setelah fakta dengan event ID 1074/41/6008.

Kali berikutnya Anda menambah fitur ke aplikasi, tanya diri sendiri sekali ini: jika WM_ENDSESSION tiba di tengah pekerjaan ini, atau dayanya ditarik, apa yang tersisa pada peluncuran berikutnya? Menulis jawaban itu ke dalam desain adalah jalur terpendek untuk tidak pernah menghabiskan pagi berdiri di depan PC perangkat dengan kepala di tangan.

Artikel terkait

Area konsultasi terkait

KomuraSoft LLC menangani desain dan implementasi penanggulangan shutdown dan kehilangan daya untuk aplikasi PC perangkat dan yang berjalan lama, investigasi akar masalah korupsi data dan insiden “ia sudah berhenti pagi harinya” yang dimulai dari restart Windows Update atau keluar, serta tinjauan desain pemrosesan berhenti layanan Windows dan pemulihan otomatis. Tidak apa-apa memulai dari tahap “sesuatu sepertinya rusak setiap kali kita shutdown, dan saya tidak tahu di mana mulai”.

Tautan referensi

  1. Microsoft Learn, WM_QUERYENDSESSION message. Tentang WM_QUERYENDSESSION dikirim pada akhir sesi dan aplikasi harus mengembalikan TRUE serta menghormati niat pengguna (bawaan DefWindowProc juga TRUE); tentang pembersihan harus ditunda sampai WM_ENDSESSION; tentang setelah 5 detik sistem menampilkan UI untuk aplikasi yang mencegah shutdown dan pengguna dapat memaksa-akhiri; arti bit ENDSESSION_LOGOFF/CLOSEAPP/CRITICAL di lParam; tentang shutdown dan restart tidak dapat dibedakan; dan tentang data harus disimpan sering agar lebih sedikit yang harus disimpan saat keluar.  2 3 4 5 6 7

  2. Microsoft Learn, Fast startup causes hibernation or shutdown to fail in Windows 10 or Windows 8.1. Tentang dengan fast startup sesi kernel tidak ditutup dan diperlakukan sebagai hibernasi, dan state kernel serta driver perangkat disimpan ke hiberfil.sys; tentang “Restart” selalu melakukan boot penuh karena state Windows yang sama sekali baru dibutuhkan; tentang fast startup diaktifkan secara bawaan dan menonaktifkannya tidak direkomendasikan; dan tentang bawaan Shutdown.exe adalah shutdown penuh, dengan opsi /hybrid menghasilkan perilaku hybrid.  2 3 4 5

  3. Microsoft Learn, ShutdownBlockReasonCreate function (winuser.h). Tentang Anda memanggilnya di awal operasi yang tidak dapat diinterupsi untuk mendaftarkan string alasan dan memanggil ShutdownBlockReasonDestroy ketika selesai; tentang ia hanya dapat dipanggil dari thread yang membuat jendela; dan tentang pengguna hanya akan membaca alasan beberapa detik, jadi string harus pendek dan jelas.  2 3

  4. Microsoft Learn, Shutdown Changes for Windows Vista. Tentang respons ke WM_QUERYENDSESSION/WM_ENDSESSION dapat ditunda 5 detik masing-masing dan pengguna kemudian dapat memilih lanjut atau batal; tentang aplikasi konsol atau aplikasi tanpa jendela terlihat tidak dapat membatalkan shutdown dan diakhiri secara otomatis setelah 5 detik tanpa respons atau respons FALSE; tentang jika blok diperlukan alasan harus didaftarkan dengan ShutdownBlockReasonCreate; dan tentang aplikasi tidak boleh bergantung pada mampu memblokir shutdown.  2 3 4

  5. Microsoft Learn, HandlerRoutine callback function. Event CTRL_C/BREAK/CLOSE/LOGOFF/SHUTDOWN yang diterima handler yang didaftarkan dengan SetConsoleCtrlHandler; tentang timeout bawaan untuk CTRL_CLOSE_EVENT sekitar 5000 milidetik dan untuk CTRL_SHUTDOWN_EVENT pada proses layanan sekitar 20000 milidetik; tentang CTRL_LOGOFF/SHUTDOWN_EVENT pada dasarnya hanya diterima layanan karena aplikasi interaktif diakhiri saat logoff; dan tentang handler berjalan pada thread terpisah.  2 3

  6. Microsoft Learn, SetConsoleCtrlHandler function. Tentang proses yang telah memuat gdi32.dll atau user32.dll diperlakukan sebagai aplikasi Windows dan handler CTRL_LOGOFF_EVENT/CTRL_SHUTDOWN_EVENT tidak dipanggil; tentang solusinya adalah membuat jendela tersembunyi dan menangani WM_QUERYENDSESSION/WM_ENDSESSION; dan tentang fungsi konsol mungkin tidak bekerja dengan benar selama penanganan sinyal.  2

  7. Microsoft Learn, .NET runtime no longer provides default termination signal handlers. Tentang dari .NET 10 runtime tidak lagi menyediakan handler bawaan untuk Windows CTRL_SHUTDOWN_EVENT/CTRL_CLOSE_EVENT (padanan Unix SIGTERM/SIGHUP); tentang penanganan bawaan OS mengakhiri aplikasi segera dan AppDomain.ProcessExit serta AssemblyLoadContext.Unloading tidak lagi menyala; dan tentang penanganan sinyal yang sesuai model aplikasi harus didaftarkan di pustaka tingkat lebih tinggi atau di kode aplikasi.  2

  8. Microsoft Learn, Service Control Handler Function. Tentang layanan yang menyatakan SERVICE_ACCEPT_PRESHUTDOWN menerima SERVICE_CONTROL_PRESHUTDOWN lebih dulu, lalu layanan SERVICE_ACCEPT_SHUTDOWN menerima SERVICE_CONTROL_SHUTDOWN; tentang masa tenggang bawaan saat shutdown sekitar 20 detik dan plafon saat restart OS adalah WaitToKillServiceTimeout; tentang nilai ini tidak boleh diperpanjang; tentang handler kontrol harus kembali dalam 30 detik, laporkan STOP_PENDING dan wait hint, dan serahkan pekerjaan panjang ke thread lain; tentang pembersihan harus selesai secepat mungkin dengan operasi UPS dalam pikiran; dan tentang SCM saat shutdown secara bawaan tidak mempertimbangkan dependensi.  2 3 4 5

  9. Microsoft Learn, SERVICE_PRESHUTDOWN_INFO structure (winsvc.h). Tentang setelah notifikasi PRESHUTDOWN SCM menunggu sampai layanan berhenti atau timeout; tentang timeout bawaan adalah 10 detik dari Windows 10 Creators Update (build 15063) ke atas dan 3 menit sebelumnya; tentang ia dikonfigurasi dengan ChangeServiceConfig2; dan tentang status dapat terus diperbarui selama SERVICE_STOP_PENDING.  2

  10. Microsoft Learn, RegisterApplicationRestart function (winbase.h). Tentang restart dapat didaftarkan untuk crash, tidak merespons, pembaruan, dan restart komputer yang menyertai pembaruan; tentang argumen baris perintah untuk restart dapat ditentukan; tentang pendaftaran harus dilakukan sebelum masalah terjadi dan selama penanganan WM_QUERYENDSESSION adalah kesempatan terakhir dalam skenario pembaruan; tentang proses di bawah 60 detik runtime tidak di-restart; tentang restart setelah crash atau hang melalui persetujuan pengguna; dan tentang melewati restart OS mensyaratkan shutdown dengan EWX_RESTARTAPPS/SHUTDOWN_RESTARTAPPS.  2 3

  11. Microsoft Learn, Winlogon automatic restart sign-on (ARSO). Tentang ketika Windows Update memulai restart otomatis ia menyimpan kredensial pengguna interaktif terakhir dan mengonfigurasi Autologon; tentang setelah restart ia secara otomatis memasukkan pengguna dan mengunci sesi; tentang kredensial yang disimpan dihapus setelah sign-in berhasil; dan tentang ia dapat dikonfigurasi dengan Group Policy (DisableAutomaticRestartSignOn dan semacamnya).  2

  12. Microsoft Learn, ReplaceFileW function (winbase.h). Tentang ReplaceFile mengemas ke dalam satu fungsi beberapa langkah setara “simpan ke berkas baru, sementara ganti nama yang asli, ganti nama berkas baru, hapus yang asli”; tentang ia mempertahankan atribut berkas asli seperti waktu pembuatan, DACL, enkripsi, kompresi, dan named stream; dan tentang cadangan, berkas yang diganti, dan berkas pengganti harus pada volume yang sama.  2

  13. Microsoft Learn, File Caching. Tentang tulis masuk ke cache sistem secara bawaan dan dicerminkan ke disk oleh lazy writing; tentang FILE_FLAG_WRITE_THROUGH menulis segera ke disk; tentang FlushFileBuffers dapat flush secara eksplisit; dan tentang metadata sistem berkas selalu di-cache, jadi mengonfirmasi metadata mensyaratkan flush atau write-through.  2 3

  14. Microsoft Learn, Troubleshoot unexpected reboots using system event logs. Tentang restart normal mencatat event ID 1074 (proses mana memulai shutdown, untuk siapa, dan untuk alasan apa); tentang restart tak terduga mencatat event ID 41 (Kernel-Power) dan 6008 (shutdown sebelumnya tak terduga); dan tentang ID ini dapat mengisolasi jenis restart.  2

  15. Microsoft Learn, PBT_APMPOWERSTATUSCHANGE event. Tentang event ini diberitahu lewat WM_POWERBROADCAST pada peralihan antara baterai dan AC atau penurunan kapasitas tersisa; dan tentang saat penerimaan Anda harus memanggil GetSystemPowerStatus dan memeriksa field SYSTEM_POWER_STATUS seperti ACLineStatus, BatteryFlag, dan BatteryLifePercent. 

Artikel terbaru dengan tag yang sama untuk mendalami topik-topik terdekat.

Halaman-halaman ini menempatkan topik dalam konteks layanan dan keputusan yang lebih luas.

Artikel ini berkaitan langsung dengan layanan berikut.

Pertanyaan yang sering diajukan

Pertanyaan yang sering muncul dalam konsultasi tentang topik artikel ini.

Masalah yang tidak hilang setelah "Shut down" hilang setelah "Restart". Mengapa?
Pada OS klien dari Windows 8 ke atas, ketika fast startup diaktifkan (bawaan pada kebanyakan PC yang mendukung hibernasi), "Shut down" memakai mekanisme yang disebut hybrid shutdown. Pengguna keluar, tetapi state kernel dan driver disimpan ke berkas hibernasi dan dipulihkan apa adanya pada boot berikutnya. Dengan kata lain, inti OS belum di-reset. "Restart", sebaliknya, selalu melakukan boot penuh, jadi masalah driver dan layanan di-reset. Tulis "Restart" ke dalam prosedur isolasi Anda, bukan "Shut down lalu nyalakan lagi". Jika Anda ingin shutdown penuh dari baris perintah, Anda dapat memakai shutdown /s.
Bisakah saya menghentikan shutdown sampai aplikasi selesai menyimpan?
Anda dapat memintanya menunggu sementara, tetapi Anda tidak dapat menghentikannya secara andal. Jika Anda mendaftarkan string alasan dengan ShutdownBlockReasonCreate hanya selama operasi yang tidak dapat diinterupsi sedang berlangsung, alasan itu muncul di layar "This app is preventing shutdown" dan pengguna dapat memutuskan apakah lanjut atau batal. Pengguna tetap dapat memilih paksa lanjut, walaupun, dan shutdown paksa atau restart yang didorong pembaruan mungkin tidak menunggu sama sekali. Pendekatan yang tepat karenanya bukan "blokir", melainkan autosave sering agar lebih sedikit data yang berisiko, plus pembersihan yang dirancang selesai dalam beberapa detik dari notifikasi keluar.
Menghentikan layanan Windows saya butuh waktu lama. Bisakah saya memperpanjang masa tenggang shutdown?
Dalam konfigurasi bawaan yang menerima SERVICE_CONTROL_SHUTDOWN, masa tenggang kira-kira 20 detik dan bergantung pada nilai registri WaitToKillServiceTimeout. Menulis ulang nilai itu dari sisi aplikasi untuk memperpanjangnya tidak direkomendasikan. Jika Anda membutuhkan masa tenggang lebih panjang, Anda dapat menyatakan SERVICE_ACCEPT_PRESHUTDOWN dan menerima SERVICE_CONTROL_PRESHUTDOWN; Anda diberitahu lebih awal daripada yang lain, dan timeout dapat dikonfigurasi dengan ChangeServiceConfig2 (bawaan adalah 10 detik dari Windows 10 Creators Update ke atas, dan 3 menit sebelumnya). PRESHUTDOWN, walaupun, menahan seluruh shutdown selama interval itu, jadi batasi pada kasus yang sungguh Anda butuhkan, dan secara mendasar rancang pekerjaan berhenti itu sendiri agar selesai dalam beberapa detik.
Apakah aman melakukan pembersihan shutdown di AppDomain.ProcessExit milik .NET?
Saya merekomendasikan untuk tidak mengandalkannya. Secara historis runtime mendaftarkan handler sinyal bawaan, dan ProcessExit menyala pada CTRL_CLOSE_EVENT dan CTRL_SHUTDOWN_EVENT, tetapi dari .NET 10 runtime tidak lagi menyediakan handler sinyal terminasi bawaan, dan ProcessExit tidak lagi menyala dalam kasus itu. Implementasikan pembersihan pada jalur notifikasi yang cocok dengan model aplikasi: aplikasi GUI memakai FormClosing atau SessionEnding (itu notifikasi fase kueri, jadi batasi pada simpan yang idempoten; pembersihan yang hanya dapat berjalan setelah sesi dikomit termasuk di hook WM_ENDSESSION); Generic Host / Worker Service memakai IHostApplicationLifetime dan StopAsync; aplikasi konsol memakai SetConsoleCtrlHandler atau PosixSignalRegistration.
Bagaimana saya menjaga berkas agar tidak rusak oleh kehilangan daya mendadak?
Kehilangan daya tidak membawa notifikasi sama sekali, jadi satu-satunya pilihan adalah menulis dengan cara yang tidak rusak tidak peduli kapan dayanya dipotong. Dasarnya adalah tidak menimpa berkas asli di tempat: tulis lengkap ke berkas sementara pada volume yang sama, flush, dan tukar dengan ReplaceFile (File.Replace di .NET). Dalam operasi biasa ini meninggalkan Anda mampu membaca baik berkas lama yang lengkap maupun berkas baru yang lengkap, tetapi atomisitas ReplaceFile melewati kehilangan daya tidak dijamin oleh spesifikasi, jadi simpan cadangan (argumen ketiga) dan implementasikan pemulihan saat muat yang memvalidasi berkas primer dan jatuh ke cadangan jika rusak. Juga, sukses dari WriteFile tidak berarti data telah mencapai disk, jadi pada titik pemeriksaan penting konfirmasi tulis dengan FlushFileBuffers atau FILE_FLAG_WRITE_THROUGH. Pada PC perangkat setup standar adalah menggabungkannya dengan UPS, mendeteksi peralihan ke baterai, dan mengantar ke shutdown yang aman.

Profil penulis

Halaman perkenalan penulis artikel.

Go Komura

Direktur KomuraSoft LLC

Berspesialisasi dalam pengembangan perangkat lunak Windows, konsultasi teknis, dan investigasi bug, terutama pada proyek dengan sistem yang sudah ada dan bug yang sulit direproduksi.

Kembali ke blog