首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 開發 Windows Service 完整教學:以定時護眼提醒為例

     編輯:關於C語言

前言

Windows 服務是在背景長期執行、不需使用者登入即可啟動的應用程式,適用於監控、排程、伺服器等場景。本文完整示範從建立專案、撰寫服務邏輯、安裝部署到偵錯的全流程,以「定時提醒休息護眼」為實務案例。

Windows Service 核心架構

  • ServiceBase:所有服務的基底類別,提供生命週期事件
  • ServiceControlManager (SCM):Windows 管理服務的核心元件
  • 生命週期方法:
    • `OnStart` - 服務啟動時執行
    • `OnStop` - 服務停止時執行
    • `OnPause`/`OnContinue` - 暫停/繼續(選擇性實作)
    • `OnShutdown` - 系統關機時

專案建立步驟

  1. Visual Studio → 新增專案 → Windows Service (.NET Framework) 或 Worker Service (.NET Core/.NET 5+)
  2. 專案名稱:CareEyeService
  3. 會自動產生 `Service1.cs`(服務主類別)與 `Program.cs`(進入點)

完整服務實作程式碼

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

關鍵技術點解析

1. Session 0 隔離問題

Windows Vista 後服務運行於 Session 0,無法直接與使用者桌面互動(MessageBox 不會顯示)。解決方式:

  • 獨立 GUI 應用程式 + 服務啟動它(`UseShellExecute = true`)
  • Named Pipe / TCP / 檔案 通訊,GUI 輪詢或接收推播
  • Windows 10 1803+ 可用 `Windows.ServiceProcess` 新 API(較複雜)

2. Timer 選擇

Timer 類型命名空間適用場景
System.Timers.TimerSystem.Timers服務端、多執行緒、精確間隔
System.Threading.TimerSystem.Threading輕量、ThreadPool 執行、無事件模型
System.Windows.Forms.TimerSystem.Windows.Forms僅限 UI 執行緒、單執行緒、不適用服務

3. 安裝程式專案

在服務設計介面右鍵 → 加入安裝程式,會產生 `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 手動啟動(開發測試建議)
    }
}

安裝、啟動、偵錯完整流程

方法一:InstallUtil(傳統 .NET Framework)

// 開發人員命令提示字元 (管理員)
cd C:\Projects\CareEyeService\bin\Debug
installutil CareEyeService.exe        # 安裝
installutil /u CareEyeService.exe     # 解除安裝

方法二:sc.exe(Windows 內建)

sc create CareEyeService binPath= "C:\Full\Path\CareEyeService.exe" start= auto
sc start CareEyeService
sc stop CareEyeService
sc delete CareEyeService

方法三:.NET Core / .NET 5+ 發布為獨立執行檔

dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true
# 產生單一 .exe,再用 sc.exe 或 InstallUtil 安裝

偵錯技巧:附加至處理序

  1. 安裝並啟動服務
  2. Visual Studio → 偵錯 → 附加至處理序
  3. 勾選「顯示所有使用者的處理序」
  4. 找到 `CareEyeService.exe` → 附加
  5. 下中斷點即可偵錯 `OnStart`、`Timer_Elapsed` 等

更好的偵錯方式:條件式編譯支援主控台模式

// 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();

部署建議:建立安裝包

  1. Visual Studio 專案類型:Setup Project 或 WiX Toolset / Advanced Installer
  2. 自訂動作:安裝階段呼叫 `InstallUtil` 或 `sc create`
  3. 設定 `InstallerClass` 屬性為 True
  4. 簽署憑證:生產環境建議代碼簽章

總結

開發 Windows Service 關鍵在於:理解生命週期、解決 Session 0 UI 隔離、正確安裝部署、建立偵錯機制。現代 .NET 開發建議使用 Worker Service 範本(.NET Core 3.1+),搭配 `IHostedService`/`BackgroundService` 基底類別,支援依賴注入、設定檔、日誌等標準化功能,並能輕鬆部署為 Linux systemd 或 Windows Service,跨平台更靈活。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved