Windows 服務的建立與維運 ── 從工作排程器的取捨到 BackgroundService 服務化
· 小村 豪 · Windows 服務, Windows, .NET, C#, BackgroundService, Generic Host, 常駐處理, 維運, 判斷表, 技術諮詢
「每 5 分鐘跑一次的工作排程器已經跟不上了」「想讓常時待命接收裝置資料的監控常駐程式一直開著」「檔案一放進資料夾就希望在幾秒內處理完」。持續接觸定期執行相關的諮詢,需求成長到一定程度後,最後幾乎都會走到這個話題。
本部落格已經連續寫了 用 Power Automate 做業務自動化、工作排程器的安全維運設計 這兩篇自動化相關文章。在工作排程器那篇的第 8 章提到「一旦需要分鐘級輪詢或常時監控,就是常駐程序的領域」這樣的分界,但實際上該怎麼建立 Windows 服務、又該怎麼讓它上線維運?如果在這裡含糊地「先做成服務再說」,就會生出「停不下來的服務」「掛掉了也沒人發現的服務」「用 LocalSystem 跑任何東西的服務」。
本文將依實務上決策的順序,整理常駐處理是否該做成 Windows 服務的判斷表、服務機制的最低限知識(SCM・啟動類型・Session 0)、以 .NET 8 的 Worker Service 進行實作、用 sc.exe 註冊與設定復原選項、執行帳戶的選擇,以及安全的停止處理。
1. 先講結論
- 取捨的判斷軸很單純。間隔數分鐘以上的定期處理,工作排程器就足夠;一旦需求裡出現常時待命(socket、具名管道、FileSystemWatcher)、秒級反應、掛掉後自動復原,就該用 Windows 服務。
- 從 Windows Vista 開始,服務會在 Session 0 執行,無法直接與使用者互動(無法顯示 UI)。如果需要 UI,就要把服務與 UI 應用程式拆開,靠行程間通訊來對話。1
- .NET 的官方建立方式,是 Worker Service 範本加上
Microsoft.Extensions.Hosting.WindowsServices的AddWindowsService(若是IHostBuilder系列,則是UseWindowsService)。可以直接當成主控台應用程式執行,讓開發、除錯比傳統的服務開發輕鬆許多。2 - .NET 6 以後,
BackgroundService內未處理的例外,預設會讓主機停止(BackgroundServiceExceptionBehavior.StopHost)。但這是「正常停止」,所以 SCM 的復原選項所帶來的重啟不會生效。想要重啟的失敗,要用非 0 的結束代碼讓行程終止。32 - 別因為圖方便就把執行帳戶設成 LocalSystem。
sc.exe create的預設值就是 LocalSystem,不留意的話就會以最強權限開始運作。4 只在本機完成的處理,優先考慮 LocalService 或虛擬帳戶;需要在網域中存取共用資源或資料庫的,gMSA 才是正解。5 - 復原選項(
sc.exe failure)和停止處理(HostOptions.ShutdownTimeout,預設 30 秒6)要在註冊時就設計好。「對停止沒有反應的服務」是維運現場最不受歡迎的存在。
2. 什麼時候該做成服務 ── 工作排程器・服務・常駐應用程式的判斷表
出現類似常駐需求時,可選的方案有 3 種:工作排程器、Windows 服務,以及「註冊到啟動項目的常駐應用程式」(常駐在通知區域那種)。先把各自的特性列出來。
| 觀點 | 工作排程器 | Windows 服務 | 常駐應用程式(啟動項目) |
|---|---|---|---|
| 啟動時機 | 依時間・事件觸發每次啟動 | OS 啟動時(登入前)就常駐 | 使用者登入時開始 |
| 是否需要登入 | 可以不需要(非互動執行) | 不需要 | 需要(登出就消失) |
| UI | 無法顯示(非互動組態時) | 無法顯示(Session 0) | 可以顯示(通知區域・對話框) |
| 常時或定期 | 定期(適合以小時到每日為單位) | 常時 | 常時(但侷限在使用者工作階段內) |
| 權限 | 每個工作各自指定執行帳戶 | 服務帳戶(第 6 章) | 登入使用者的權限 |
| 監控・復原 | 歷程記錄+自行通知 | SCM 的復原選項(自動重啟) | 沒有(要自己做) |
| 部署的工夫 | 用 XML/PowerShell 註冊 | sc.exe/安裝程式 | 註冊到 Run 機碼等 |
判斷的軸線如下。
- 每天數次到以小時為單位的定期處理,且處理之間不需要保留狀態,用工作排程器就好。特意把它做成服務、自己管理計時器是過度設計,工作排程器那篇文章 裡寫的維運設計成本要低得多。
- 本質上是常時待命──TCP 或管道的要求等待、FileSystemWatcher 資料夾監控、佇列的逐一處理、秒級反應──就用 Windows 服務。如果你開始用「每 5 分鐘一個工作」把處理切碎去輪詢,那就是在劣化重造常駐程序的訊號。
- UI 才是主體(通知區域操作、對使用者彈出視窗)就用常駐應用程式。但它必須登入才會運作,所以不適合伺服器類用途。如果「背景處理需要常時運作,但也需要 UI」,就按下一章所說,拆成服務與 UI 應用程式兩個行程。
還有一個容易被忽略的判斷依據是復原。工作排程器的工作即使失敗,也只能放著等到下次排程;服務則有 SCM 幫忙處理自動重啟(第 5 章)。「就算夜裡掛了,也希望能在早上之前自行恢復」這種需求,本身就足以成為做成服務的理由。
3. 服務機制的最低限知識 ── SCM・啟動類型・Session 0
3.1 SCM 與啟動類型
Windows 服務背後的莊家是服務控制管理器(SCM)。它管理服務的登記簿,仲介啟動、停止的要求,並在失敗時執行復原動作。服務行程必須按照規約與 SCM 對話,但這部分已經由 .NET 的函式庫代勞(第 4 章),我們在設計上要決定的,只有「啟動類型」「執行帳戶」「復原」「停止」這 4 點。
啟動類型(Startup type)有 4 種可選。4
| 啟動類型 | 行為 | 適用場合 |
|---|---|---|
| 自動(auto) | OS 啟動時開始 | 常時運作服務的基本選項 |
| 自動(延遲啟動,delayed-auto) | 在其他自動服務之後稍晚開始 | 業務服務優先選這個,避免啟動剛完成時的擁擠與依賴對象尚未就緒 |
| 手動(demand) | 只在被要求時才開始 | 由其他應用程式喚起的輔助服務 |
| 已停用(disabled) | 無法啟動 | 用於停止措施、封存 |
業務服務應該以延遲啟動為預設值。OS 剛啟動時,網路、資料庫、其他服務都還沒到齊,如果用「自動」搶最快啟動,很容易在第一次連線時就失敗。用延遲啟動拉開時間差,再靠後面提到的 ExecuteAsync 內部重試機制吸收連線失敗,這種雙重保障會比較穩定。
另外還要知道啟動逾時這件事。SCM 不會無限期等待服務回報啟動完成。超過預設逾時(ServicesPipeTimeout,30 秒)就會記錄事件 7000/7011,並視為啟動失敗。7 也就是說,像資料庫連線重試、建構大型快取這類沉重的初始化,不該放在「啟動處理」裡執行。原則是讓啟動立刻完成,沉重的處理留給主體迴圈(ExecuteAsync)去做。
3.2 Session 0 隔離 ── 服務無法顯示 UI
從 Windows Vista 開始,服務會在名為 Session 0 的隔離工作階段中執行,無法直接與使用者互動。1 就算在服務裡呼叫 MessageBox.Show 或顯示表單,登入中使用者的畫面上也不會出現任何東西。更糟的是,它會一直等待沒有人能按下的「確定」按鈕,讓整個處理卡住,這是經典的當機成因。把舊程式碼搬進服務時,一定要確認是否還殘留著原本用來顯示錯誤的訊息方塊。
需要 UI 時的正解,是把服務(Session 0)與 UI 應用程式(使用者工作階段)拆成兩個行程,靠行程間通訊(IPC)對話。這也是 Microsoft 官方建議的設計,用具名管道之類的 IPC,讓 UI 端把結果回傳給服務。1 該選哪種 IPC 手段,整理在同日發布的姊妹文章「行程間通訊的判斷表」裡。把 UI 應用程式端也放到 Generic Host 上,就能讓兩個行程在 DI、記錄、設定的架構上保持一致(參見「在桌面應用程式中使用 Generic Host 與 BackgroundService」)。
4. .NET 中的建立方式 ── Worker Service 與 AddWindowsService
4.1 範本與 Program.cs
.NET 的服務開發,要從 Worker Service 範本開始。它已內建在 SDK 中,用 dotnet new worker 就能產生雛形,接著只要加入 Microsoft.Extensions.Hosting.WindowsServices 套件並呼叫 AddWindowsService,就準備好可以當 Windows 服務運作了。2 作為基礎的 Generic Host 機制,在「什麼是 .NET Generic Host」裡有說明。
using App.MonitorService;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
// 加入以 Windows 服務啟動時與 SCM 對話用的生命週期。
// 若以主控台方式啟動,則維持原本的 ConsoleLifetime 運作
builder.Services.AddWindowsService(options =>
{
options.ServiceName = "KsMonitor";
});
builder.Services.AddSingleton<MeasurementQueue>();
builder.Services.AddHostedService<MonitorWorker>();
IHost host = builder.Build();
host.Run();
如果是使用 Host.CreateDefaultBuilder 的傳統風格專案,就改成呼叫 IHostBuilder 擴充方法 UseWindowsService(),作用相同。
有一個和工作排程器文章裡「開始(選用)」問題同類型的陷阱。以服務方式啟動時,目前工作目錄是 C:\Windows\System32。如果 appsettings.json 之類的檔案是以相對路徑為前提去讀取,就會出現「主控台可以動,服務卻讀不到設定」的狀況。請讓設定檔以執行檔所在位置為基準來解析,或依官方教學的指示,在註冊時的 binpath 中加上 --contentRoot,明確指定內容根目錄。2
4.2 ExecuteAsync ── stoppingToken 與例外設計
主體要寫在繼承自 BackgroundService 的 ExecuteAsync 裡。這裡該決定的,就只有「如何回應停止要求」和「如何處理例外」這兩件事。
namespace App.MonitorService;
public sealed class MonitorWorker(
MeasurementQueue queue,
ILogger<MonitorWorker> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
// 從佇列取出一筆進行處理。等待時務必一律傳入 token
var item = await queue.DequeueAsync(stoppingToken);
await ProcessAsync(item, stoppingToken);
}
catch (Exception ex) when (ex is IOException or TimeoutException)
{
// 可以繼續運作的失敗:記錄後間隔一段時間再重試
logger.LogError(ex, "處理失敗,將於 30 秒後重試。");
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
}
}
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
// 來自 SCM 的停止要求,屬於正常流程,不做任何事。
// 用 when 子句限定為 stoppingToken 觸發的原因,是為了避免內部處理
// 逾時等狀況丟出的取消被誤判成「正常停止」,導致工作執行緒
// 悄悄停下、而主機卻繼續存活
}
catch (Exception ex)
{
// 未預期的失敗:預設的 StopHost 會讓主機「正常」停止,
// 導致 SCM 的復原選項(自動重啟)不會觸發。
// 用非 0 的結束代碼讓行程終止,告訴 SCM「這是失敗」
logger.LogCritical(ex, "發生無法復原的錯誤,服務即將終止。");
Environment.Exit(1);
}
}
private Task ProcessAsync(Measurement item, CancellationToken token)
=> throw new NotImplementedException();
}
- 把
stoppingToken傳給所有等待。Task.Delay、I/O、佇列等待都要傳。只要有一處忽略了這個 token 的長時間等待,那裡就會變成「對停止沒有反應的服務」的溫床(第 7 章)。 - 把可以繼續運作的失敗和無法復原的失敗分開。 網路中斷、暫時性逾時之類的狀況,要在迴圈內攔截、記錄並重試。只是吞掉例外再
continue的catch (Exception),只會做出一個悄悄空轉的服務。該怎麼分類的思路,整理在「遇到未預期例外時該終止還是繼續的判斷表」裡。 - 要知道未處理例外的預設行為。 .NET 6 之前,從
ExecuteAsync流出的例外會憑空消失,服務看起來還在跑,實際上卻什麼都不做,變成「僵屍」。.NET 6 以後,預設行為變成BackgroundServiceExceptionBehavior.StopHost,例外會先記錄到記錄檔,再讓主機停止。3 但這個停止屬於「正常停止」,就算設定了復原選項也不會重啟。想靠復原選項恢復的失敗,就要像上面的程式碼那樣,用Environment.Exit(1)明確地讓它異常終止,這才是官方教學所示範的做法。2
4.3 可以直接以主控台執行 ── 開發變輕鬆的最大原因
AddWindowsService 只會在以 Windows 服務方式啟動時,才切換成服務專用的生命週期。也就是說,同一個 exe 在 Visual Studio 按 F5 或用 dotnet run 時,會當成一般的主控台應用程式執行。中斷點照樣能命中,按下 Ctrl+C 就能在本機驗證從停止要求到 StopAsync 的整個流程。
不過,主控台上能動,不代表服務也一定能動。執行帳戶的差異(第 6 章)、Session 0、目前工作目錄——只有這 3 點,一定要以服務身分在實機上驗證。「主控台能動、服務卻不能動」的原因,幾乎都會收斂到這 3 點上。
5. 註冊與維運 ── sc.exe・復原選項・事件記錄
5.1 用 sc.exe create 註冊
把發布(dotnet publish,發布成單一檔案會比較好處理)出來的 exe,用 sc.exe create 註冊到 SCM。要在系統管理員權限的 PowerShell 中執行。
sc.exe create "KsMonitor" binpath= "C:\Services\KsMonitor\KsMonitor.exe" start= delayed-auto obj= "NT AUTHORITY\LocalService" displayname= "KS 量測資料匯入服務"
sc.exe description "KsMonitor" "將量測資料匯入並登錄到資料庫的常駐服務(負責單位:資訊系統部)"
先說一個經典陷阱。binpath= 或 start= 的等號後面必須要有空白。等號之前是選項名稱,選項名稱與值之間的空白在語法上是必須的,這是 sc.exe 特有的規則(省略就會失敗)。4 另外,省略 obj= 時的預設值是 LocalSystem。4 什麼都不設定就直接註冊,就會以最強權限開始運作,所以請在註冊時就完成第 6 章提到的選擇。
設定 description 看似不起眼卻很重要。留下足夠資訊,讓幾年後打開 services.msc 的人能判斷「這是什麼服務、能不能停、該問誰」,之後的調查成本就能省下來。移除時要先停止服務,再執行 sc.exe delete "KsMonitor"。2
如果部署對象有多台機器,或是需要定期更新(替換檔案),就儘早改用安裝程式(MSI/WiX 的 ServiceInstall 等)把註冊、更新、移除整套處理好,而不是靠 sc.exe 手動操作。手動註冊的操作手冊,一定會產生某台漏了一步的機器。
5.2 復原選項 ── 「掛掉就重啟」交給 SCM 處理
服務化的一大優點,就是 SCM 內建的復原選項。它讓你可以用宣告式的方式,設定行程異常終止時該做什麼。2
sc.exe failure "KsMonitor" reset= 86400 actions= restart/60000/restart/60000/restart/300000
這個範例的意思是「第 1、第 2 次失敗,60 秒後重啟;第 3 次以後,5 分鐘後重啟;失敗計數器每 24 小時重置一次」。用 GUI(services.msc 內容 →「復原」分頁)也能做同樣的設定。在自己動手打造「監看工作」或監控腳本之前,請先善用這個標準功能。
有兩點要注意。第一,如同前一章所說,行程必須以非 0 結束代碼終止才會觸發復原。StopHost 造成的正常停止不在復原範圍內。第二,如果是啟動後必定掛掉的狀態(設定錯誤、資料庫連不上),就會變成重啟循環。這也是為什麼第 3 次以後的間隔要拉長,同時要事先做好「為什麼會掛掉」能留在事件記錄裡(下一節)的準備。
5.3 事件記錄與檔案記錄並用
Host.CreateApplicationBuilder 在 Windows 上會自動加入 EventLog 記錄提供者。而預設只有 Warning 以上等級的訊息才會送到事件記錄。2 很容易讓人誤以為「做成服務之後 Information 記錄全都不見了」,但其實不是消失,而是被事件記錄的預設篩選器擋掉了。如果也想記錄啟動、停止之類的維運事件,就要在 appsettings.json 中明確設定 EventLog 提供者的等級。
{
"Logging": {
"EventLog": {
"SourceName": "KsMonitor",
"LogLevel": {
"Default": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
}
有一點要注意。事件來源(Event Source)如果沒有事先註冊就無法寫入,而註冊需要系統管理員權限。省略 SourceName 也逃不掉——因為 AddWindowsService 預設會用應用程式名稱當作來源名稱2,所以無論如何都是從「未註冊的來源」開始。像 LocalService 這種最小權限帳戶,在執行期無法建立來源,一旦建立失敗,事件記錄就會什麼都寫不進去,維運卻已經開始運作。註冊工作請交給安裝程式(或具系統管理員權限的設定流程)處理。這和工作排程器文章第 6 章寫的「把 CreateEventSource 分離到設定流程那一側」是同一件事。
區分使用的原則是:事件記錄只放「維運人員該看的東西」(啟動、停止、失敗、復原),詳細的處理追蹤則交給自己維護的檔案記錄。自行維護檔案記錄時的最低要求(輪替、寫入失敗時的行為),整理在「自製記錄器的最低要求」;行程整個掛掉時該如何留下證據的設計,整理在「當機時留下記錄與傾印檔的設計」。因為在靠復原選項自動重啟的架構下,掛掉那一刻的記錄有沒有留在現場,會是唯一的線索。
6. 執行帳戶 ── 別因為圖方便就選 LocalSystem
服務會在指定帳戶的安全性內容下運作,SCM 在啟動時會用該帳戶登入,並把 token 附加到行程上。8 這就是工作排程器文章第 3 章寫的「以誰的身分執行」,換成服務版的說法。先把各個選項列出來。
| 帳戶 | 權限 | 在網路上的身分 | 密碼管理 | 適用場合 |
|---|---|---|---|---|
| LocalSystem | 極強(相當於 OS 層級) | 電腦帳戶 | 不需要 | 原則上要避免。因為是 sc.exe create 的預設值,要特別注意 |
| LocalService | 最小限 | 匿名(無法存取共用資源) | 不需要 | 本機完成處理的第一候選 |
| NetworkService | 最小限 | 電腦帳戶 | 不需要 | 需要存取網域內資源的輕量處理 |
| 虛擬帳戶(NT SERVICE\服務名稱) | 只有授予的部分 | 電腦帳戶 | 不需要 | 可依服務單位授予 ACL,對本機資源採最小權限 |
| gMSA | 只有授予的部分 | gMSA 本身 | 由 DC 自動管理 | 需要在網域中存取共用資源/資料庫的服務首選 |
判斷的重點有 3 個。
- 不要因為「這樣能動」就選 LocalSystem。 服務的漏洞會直接等同於整台機器被接管。是否真的需要相當於系統管理員的權限,可以直接套用「什麼時候才真的需要系統管理員權限」裡的整理。多數情況下,需要的頂多是「寫入特定資料夾」這種程度,只要把該資料夾的 ACL 授予虛擬帳戶(
NT SERVICE\KsMonitor)就夠了。虛擬帳戶不需要建立,也不需要密碼管理。5 - 網路共用的陷阱。 LocalService 在網路上會變成匿名身分,所以存取
\\server\share會失敗。NetworkService、虛擬帳戶、LocalSystem 都會以電腦帳戶(DOMAIN\機器名稱$)的身分出現在網路上5,所以共用端要對這個電腦帳戶同時開放共用權限與 NTFS 權限。「明明給了使用者權限,但只有服務讀不到」的真相,多半就是這個。先決定好「要以誰的身分出現在網路上」,再去設定共用端。 - 要用一般使用者帳戶運作,就要連密碼運用一起考慮。 使用專用帳戶時,需要「以服務登入」的權利,而且密碼一旦過期,登入就會失敗,服務也就無法啟動。8 這是「密碼變更讓工作悄悄死掉」問題的服務版。如果是網域環境,用密碼由網域端自動管理的 gMSA,把這整個問題一起消掉才是正解(和工作排程器文章 3.2 節的結論一致)。
7. 安全停止 ── ShutdownTimeout 與進行中處理
先掌握停止的流程。當 services.msc、sc.exe stop,或是 OS 關機讓 SCM 發出停止要求時,.NET 的服務生命週期會把它轉換成主機停止(StopApplication),stoppingToken 會被取消,ExecuteAsync 跳出,接著呼叫各服務的 StopAsync。主機等待這一連串停止處理的時間,就是 HostOptions.ShutdownTimeout,預設是 30 秒。69 如果來不及完成,就會在後續整理工作尚未完成的情況下強行停止。
如果 30 秒不足以寫完一筆進行中的處理,就從所需時間反推、延長這個逾時。
builder.Services.Configure<HostOptions>(options =>
{
// 從寫完一筆進行中處理所需的最長時間反推
options.ShutdownTimeout = TimeSpan.FromSeconds(90);
});
在此之上,停止處理的設計要注意 3 點。
- 先決定好收到停止要求後的執行順序。 停止接受新工作 → 讓進行中的處理完成(或在安全的斷點中斷,並以能夠續傳的方式記錄下來) → 清理連線與暫存檔案。沒有決定這個順序,就會走向兩個極端:一看到 token 就把所有東西全丟掉,或是想全部做完而導致逾時。
- 不要做出「對停止沒有反應的服務」。 只要有一處忽略 token 的長迴圈或長時間等待,就會變成在 SCM 上卡在「停止中」的服務。這會妨礙 Windows Update 的重新啟動,並在每次伺服器計畫性停機時都需要手動 kill,是維運現場最不受歡迎的缺陷。如同第 4 章所說,請在每個處理節點檢查 token,並把
CancellationToken傳給長時間的 I/O。用主控台方式執行的話,按 Ctrl+C 就能原樣測試這個停止流程,建議把「按下 Ctrl+C 後幾秒內結束」納入發行前的測試項目。 - 把「即使被 kill 也不會壞掉」的設計,看得比停止處理做得多細緻更重要。 斷電或強制終止,不管停止處理做得多完善都還是會發生。交易、原子性的檔案寫入、可重跑的處理單位——「就算中途死掉,下次啟動時也能保持一致」才是本體,做得細緻的停止處理,終究只是禮貌問題。
8. 總結
判斷很單純:定期處理用工作排程器;需要常時待命、秒級反應、自動復原就用 Windows 服務;UI 才是主體就用常駐應用程式。一旦決定做成服務,用 .NET 的 Worker Service 和 AddWindowsService,採取「以主控台開發、以服務部署」的形式,就是目前的標準做法。
在註冊時需要當作設計決定的有 4 點——啟動類型(業務服務用延遲啟動)、執行帳戶(避開 LocalSystem,改用虛擬帳戶或 gMSA)、復原選項(sc.exe failure 搭配 Environment.Exit(1))、停止(ShutdownTimeout 與進行中處理的收尾)。只要掌握這 4 點,加上尊重 stoppingToken 的 ExecuteAsync 寫法,Windows 服務就不再是「特別難做的東西」。反過來說,沒有決定這 4 點就開始運作的服務,幾年後往往會帶著「停不掉、掛了也沒人發現、權限太強不敢動」三重苦回來找你。要重整既有服務,從盤點這 4 點開始,也是最快的捷徑。
相關文章
相關諮詢領域
合同會社小村軟體提供常駐處理的設計審查(包含從工作排程器遷移的判斷),以及「對停止沒有反應」「不知不覺就掛掉」的 Windows 服務調查與重整相關諮詢。
參考連結
-
Microsoft Learn,Interactive Services。說明 Windows Vista 以後服務無法直接與使用者互動、服務會在 Session 0 中執行,以及需要 UI 時建議採用另一個 GUI 應用程式行程搭配 IPC 的設計。 ↩ ↩2 ↩3
-
Microsoft Learn,Create Windows Service using BackgroundService。把 Worker Service 做成 Windows 服務的官方教學,說明
AddWindowsService、EventLog 提供者預設等級為 Warning、用sc.exe進行註冊/復原/移除,以及復原選項需要非 0 結束代碼。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 -
Microsoft Learn,.NET 6 breaking change: Exception handling in hosting。說明 .NET 6 以後,
BackgroundService.ExecuteAsync的未處理例外會先記錄到記錄檔,再依預設讓主機停止(BackgroundServiceExceptionBehavior.StopHost)。 ↩ ↩2 -
Microsoft Learn,sc.exe create。說明
start=(auto/demand/delayed-auto 等)與obj=(預設為 LocalSystem)的規格,以及選項的等號後面必須有空白(省略會失敗)。 ↩ ↩2 ↩3 ↩4 -
Microsoft Learn,Service Accounts in Windows Server。說明虛擬帳戶(
NT SERVICE\服務名稱)不需要密碼管理,會用電腦帳戶的憑證存取網路,以及 gMSA 的密碼由網域端自動管理。 ↩ ↩2 ↩3 -
Microsoft Learn,.NET Generic Host。說明主機關閉處理的流程,以及停止時會等待可設定的關閉逾時(預設 30 秒)。 ↩ ↩2
-
Microsoft Learn,A service does not start, and events 7000 and 7011 are logged。說明 SCM 只會等待
ServicesPipeTimeout指定的時間讓服務啟動,超過就會記錄事件 7000/7011,以及調高預設逾時的步驟。 ↩ -
Microsoft Learn,Service User Accounts。說明服務會在指定帳戶的安全性內容下執行,SCM 在啟動時會進行登入,密碼過期會導致服務無法啟動,以及 LocalService/NetworkService/LocalSystem 這幾個特殊帳戶。 ↩ ↩2
-
Microsoft Learn,HostOptions.ShutdownTimeout Property。說明用來控制
StopAsync預設逾時的屬性定義。 ↩
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
如何理解 Windows 的工作階段隔離 ── Session 0・RDP・多使用者同時執行
整理 Windows 應用程式開發者容易混淆的「工作階段」概念。說明服務無法顯示 UI 的 Session 0 隔離原因、RDP 連線時工作階段的行為、具名物件的工作階段隔離,以及共用 PC・RDS 環境常見的設計失誤,從實務角度解說。
業務應用程式的日期時間與時區 ── 從 DateTime 的陷阱到 UTC 儲存原則、測試設計
伺服器搬遷後時間整整差了 9 個小時、只有海外據點的日期會變成前一天──本文從 DateTime 的 Kind 與隱式轉換整理日期時間事故的根本原因。內容涵蓋與 DateTimeOffset 的取捨、UTC 儲存與 ISO 8601 原則、TimeZoneInfo 與夏令時...
Windows 的行程間通訊該怎麼選 ── 具名管道 / TCP / gRPC / 共享記憶體 / COM 判斷表
整理 Windows 應用程式之間該如何選擇溝通方式。以判斷表整理具名管道、本機 TCP、gRPC、共享記憶體、檔案協作、COM 各自的強項與陷阱,並從實務角度說明 UI+服務分離・32bit/64bit 橋接・權限邊界等典型架構,以及具名管道的實作範例。
在 C# 中將 SQLite 用於業務應用程式 ── WAL 模式・排他控制・損毀對策・與 EF Core 的取捨
整理用 Microsoft.Data.Sqlite 把 SQLite 整合進業務用 Windows 應用程式的實務知識:連線字串與連線池、WAL 模式的原理、SQLITE_BUSY 與寫入路徑單一化、用交易加速、型別對應的陷阱、用 VACUUM INTO 備份,一直到該在什...
Windows應用程式資料儲存位置怎麼選 ── SQLite / JSON / 登錄檔 / Access 判斷表
Windows桌面應用程式的資料該存在哪裡、用什麼格式儲存?本文整理AppData/ProgramData的使用區分,以及SQLite、JSON檔案、登錄檔、Access(.accdb)各自的優勢與陷阱,並附判斷表,從實務角度說明防止資料損毀與位元數問題等注意事項。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
Generic Host & 應用程式架構
整理 Generic Host、BackgroundService、DI、組態、日誌以及應用程式生命週期設計的主題頁面。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
支援包含常駐處理、設備連動、運作日誌與可維護結構的 Windows 桌面應用程式。
技術諮詢 & 設計審查
協助釐清設計方向、架構邊界、生命週期責任,以及既有 Windows 資產的處理方式。
常見問題
整理諮詢這個主題時常見的問題。
- 工作排程器和 Windows 服務該怎麼取捨?
- 如果是間隔數分鐘以上的定期處理,且處理之間不需要保留狀態,用工作排程器就足夠了。一旦需求裡出現 TCP 或管道的常時待命、FileSystemWatcher 資料夾監控、秒級反應、掛掉後自動復原,就該用 Windows 服務。如果你開始用「每 5 分鐘一個工作」來切碎輪詢,這就是在劣化重造常駐程序的訊號。如果 UI 才是主體(例如透過通知區域操作),那麼只在登入期間運作的常駐應用程式,就是第三個選項。
- Windows 服務可以顯示 UI(畫面)嗎?
- 不行。從 Windows Vista 開始,服務會在稱為 Session 0 的隔離工作階段中執行,無法直接與使用者互動。就算在服務裡呼叫 MessageBox.Show,使用者的畫面上也不會出現任何東西,反而會因為一直等待沒有人能按下的「確定」按鈕而讓處理停住,造成當機。如果需要 UI,正確做法是把服務與 UI 應用程式拆成兩個行程,透過具名管道之類的行程間通訊來對話。把舊程式碼搬到服務裡時,一定要檢查是否還殘留著用來顯示錯誤的訊息方塊。
- .NET 要怎麼建立 Windows 服務?
- 官方做法是先從 Worker Service 範本(dotnet new worker)開始,加入 Microsoft.Extensions.Hosting.WindowsServices 套件後呼叫 AddWindowsService。同一個 exe 在 Visual Studio 按 F5 或用 dotnet run 執行時,會當成一般的主控台應用程式運作,開發、除錯因此輕鬆許多。註冊要用 sc.exe create,但要注意它獨特的規則:binpath= 或 start= 的等號後面必須有空白,而且省略 obj= 時預設會是 LocalSystem(最強權限)。服務啟動時的目前工作目錄是 C:\Windows\System32,所以設定檔要以執行檔所在位置為基準來解析。
- BackgroundService 發生例外時,服務會怎麼樣?
- .NET 6 以後,從 ExecuteAsync 流出的未處理例外,預設行為(BackgroundServiceExceptionBehavior.StopHost)是先記錄到記錄檔,再讓主機停止。但這會被視為「正常停止」,所以不會觸發 SCM 的復原選項(自動重啟)。如果希望復原選項能重啟服務,就必須像 Environment.Exit(1) 那樣,用非 0 的結束代碼讓行程明確地異常終止。像網路中斷這類可以繼續運作的失敗,應該在迴圈內攔截、記錄並重試,並在設計上與無法復原的失敗區分開來。
作者檔案
本文作者的個人檔案頁面。
Go Komura
小村軟體有限公司 代表
以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。