Windows 服務是在背景長期執行、不需使用者登入即可啟動的應用程式,適用於監控、排程、伺服器等場景。本文完整示範從建立專案、撰寫服務邏輯、安裝部署到偵錯的全流程,以「定時提醒休息護眼」為實務案例。
CareEyeServiceusing System;
using System.ServiceProcess;
using System.Threading;
using System.Timers; // 建議使用 System.Timers.Timer 而非 Thread.Sleep
namespace CareEyeService
{
public partial class CareEyeService : ServiceBase
{
private System.Timers.Timer _timer;
private readonly TimeSpan _remindInterval = TimeSpan.FromHours(1);
private DateTime _lastRemindTime = DateTime.MinValue;
public CareEyeService()
{
InitializeComponent();
this.ServiceName = "CareEyeService";
this.CanPauseAndContinue = true;
this.AutoLog = true; // 自動記錄事件日誌
}
protected override void OnStart(string[] args)
{
EventLog.WriteEntry("CareEye 服務已啟動", EventLogEntryType.Information);
_timer = new System.Timers.Timer
{
Interval = 60000, // 每分鐘檢查一次
AutoReset = true
};
_timer.Elapsed += Timer_Elapsed;
_timer.Start();
}
protected override void OnStop()
{
_timer?.Stop();
_timer?.Dispose();
EventLog.WriteEntry("CareEye 服務已停止", EventLogEntryType.Information);
}
protected override void OnPause()
{
_timer?.Stop();
EventLog.WriteEntry("CareEye 服務已暫停", EventLogEntryType.Information);
}
protected override void OnContinue()
{
_timer?.Start();
EventLog.WriteEntry("CareEye 服務已繼續", EventLogEntryType.Information);
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
try
{
// 每小時整點提醒(可改為固定間隔)
if (DateTime.Now - _lastRemindTime >= _remindInterval)
{
ShowReminder();
_lastRemindTime = DateTime.Now;
}
}
catch (Exception ex)
{
EventLog.WriteEntry($"提醒執行錯誤: {ex.Message}", EventLogEntryType.Error);
}
}
private void ShowReminder()
{
// 服務運行於 Session 0,無法直接顯示 UI
// 解決方案:使用 WTSSendMessage 或啟動獨立 GUI 程式
// 方案一:WTSSendMessage (需引用 Wtsapi32.dll)
// WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, sessionId, ...)
// 方案二:啟動桌面應用程式(推薦,分離關注點)
StartReminderApp();
// 方案三:寫入事件日誌,由獨立監控工具讀取顯示
EventLog.WriteEntry("護眼提醒:請休息 5 分鐘,眺望遠方", EventLogEntryType.Information);
}
private void StartReminderApp()
{
try
{
// 假設有一個獨立的 ReminderApp.exe 專門負責顯示提醒視窗
// 可透過命令列參數傳遞訊息內容
string appPath = AppDomain.CurrentDomain.BaseDirectory + "ReminderApp.exe";
if (File.Exists(appPath))
{
Process.Start(new ProcessStartInfo
{
FileName = appPath,
Arguments = "\"為了愛護您的眼睛,請暫時休息 5 分鐘並向遠處眺望!\"",
UseShellExecute = true // 重要:讓它在使用者 Session 執行
});
}
}
catch (Exception ex)
{
EventLog.WriteEntry($"啟動提醒程式失敗: {ex.Message}", EventLogEntryType.Error);
}
}
}
}
Windows Vista 後服務運行於 Session 0,無法直接與使用者桌面互動(MessageBox 不會顯示)。解決方式:
| Timer 類型 | 命名空間 | 適用場景 |
|---|---|---|
| System.Timers.Timer | System.Timers | 服務端、多執行緒、精確間隔 |
| System.Threading.Timer | System.Threading | 輕量、ThreadPool 執行、無事件模型 |
| System.Windows.Forms.Timer | System.Windows.Forms | 僅限 UI 執行緒、單執行緒、不適用服務 |
在服務設計介面右鍵 → 加入安裝程式,會產生 `ProjectInstaller.cs`:
[RunInstaller(true)]
public partial class ProjectInstaller : Installer
{
public ProjectInstaller()
{
InitializeComponent();
// 服務程序安裝器
serviceProcessInstaller1.Account = ServiceAccount.LocalSystem; // 或 User 需輸入帳密
serviceProcessInstaller1.Username = null;
serviceProcessInstaller1.Password = null;
// 服務安裝器
serviceInstaller1.ServiceName = "CareEyeService";
serviceInstaller1.DisplayName = "護眼提醒服務";
serviceInstaller1.Description = "定時提醒使用者休息保護視力";
serviceInstaller1.StartType = ServiceStartMode.Automatic; // 自動啟動
// ServiceStartMode.Manual 手動啟動(開發測試建議)
}
}
// 開發人員命令提示字元 (管理員)
cd C:\Projects\CareEyeService\bin\Debug
installutil CareEyeService.exe # 安裝
installutil /u CareEyeService.exe # 解除安裝
sc create CareEyeService binPath= "C:\Full\Path\CareEyeService.exe" start= auto
sc start CareEyeService
sc stop CareEyeService
sc delete CareEyeService
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true
# 產生單一 .exe,再用 sc.exe 或 InstallUtil 安裝
// Program.cs
static void Main(string[] args)
{
if (Environment.UserInteractive)
{
// 主控台模式:方便偵錯
var service = new CareEyeService();
service.StartConsole(args); // 需在服務類別加入此 public 方法
Console.WriteLine("按任意鍵停止...");
Console.ReadKey();
service.StopConsole();
}
else
{
// 服務模式
ServiceBase.Run(new CareEyeService());
}
}
// CareEyeService.cs 內新增
public void StartConsole(string[] args) => OnStart(args);
public void StopConsole() => OnStop();
開發 Windows Service 關鍵在於:理解生命週期、解決 Session 0 UI 隔離、正確安裝部署、建立偵錯機制。現代 .NET 開發建議使用 Worker Service 範本(.NET Core 3.1+),搭配 `IHostedService`/`BackgroundService` 基底類別,支援依賴注入、設定檔、日誌等標準化功能,並能輕鬆部署為 Linux systemd 或 Windows Service,跨平台更靈活。