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.WindowsServicesAddWindowsService(若是 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 與例外設計

主體要寫在繼承自 BackgroundServiceExecuteAsync 裡。這裡該決定的,就只有「如何回應停止要求」和「如何處理例外」這兩件事。

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 章)。
  • 把可以繼續運作的失敗和無法復原的失敗分開。 網路中斷、暫時性逾時之類的狀況,要在迴圈內攔截、記錄並重試。只是吞掉例外再 continuecatch (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 點,加上尊重 stoppingTokenExecuteAsync 寫法,Windows 服務就不再是「特別難做的東西」。反過來說,沒有決定這 4 點就開始運作的服務,幾年後往往會帶著「停不掉、掛了也沒人發現、權限太強不敢動」三重苦回來找你。要重整既有服務,從盤點這 4 點開始,也是最快的捷徑。

相關文章

相關諮詢領域

合同會社小村軟體提供常駐處理的設計審查(包含從工作排程器遷移的判斷),以及「對停止沒有反應」「不知不覺就掛掉」的 Windows 服務調查與重整相關諮詢。

參考連結

  1. Microsoft Learn,Interactive Services。說明 Windows Vista 以後服務無法直接與使用者互動、服務會在 Session 0 中執行,以及需要 UI 時建議採用另一個 GUI 應用程式行程搭配 IPC 的設計。  2 3

  2. Microsoft Learn,Create Windows Service using BackgroundService。把 Worker Service 做成 Windows 服務的官方教學,說明 AddWindowsService、EventLog 提供者預設等級為 Warning、用 sc.exe 進行註冊/復原/移除,以及復原選項需要非 0 結束代碼。  2 3 4 5 6 7 8 9

  3. Microsoft Learn,.NET 6 breaking change: Exception handling in hosting。說明 .NET 6 以後,BackgroundService.ExecuteAsync 的未處理例外會先記錄到記錄檔,再依預設讓主機停止(BackgroundServiceExceptionBehavior.StopHost)。  2

  4. Microsoft Learn,sc.exe create。說明 start=(auto/demand/delayed-auto 等)與 obj=(預設為 LocalSystem)的規格,以及選項的等號後面必須有空白(省略會失敗)。  2 3 4

  5. Microsoft Learn,Service Accounts in Windows Server。說明虛擬帳戶(NT SERVICE\服務名稱)不需要密碼管理,會用電腦帳戶的憑證存取網路,以及 gMSA 的密碼由網域端自動管理。  2 3

  6. Microsoft Learn,.NET Generic Host。說明主機關閉處理的流程,以及停止時會等待可設定的關閉逾時(預設 30 秒)。  2

  7. Microsoft Learn,A service does not start, and events 7000 and 7011 are logged。說明 SCM 只會等待 ServicesPipeTimeout 指定的時間讓服務啟動,超過就會記錄事件 7000/7011,以及調高預設逾時的步驟。 

  8. Microsoft Learn,Service User Accounts。說明服務會在指定帳戶的安全性內容下執行,SCM 在啟動時會進行登入,密碼過期會導致服務無法啟動,以及 LocalService/NetworkService/LocalSystem 這幾個特殊帳戶。  2

  9. Microsoft Learn,HostOptions.ShutdownTimeout Property。說明用來控制 StopAsync 預設逾時的屬性定義。 

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

常見問題

整理諮詢這個主題時常見的問題。

工作排程器和 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 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽