使用C#簡單製作一個看門狗程式

使用C#簡單製作一個看門狗程式

在某些特殊專案中,軟體可能是無人值守的,如果程式莫名其妙掛了或者行程被幹掉了等等,這時開發一個看門狗程式是非常有必要的

最後更新 2022/11/11 上午11:20
傲慢与偏见
預計閱讀 6 分鐘
分類
.NET
標籤
.NET C#

本文由網友投稿。

作者:傲慢與偏見

原文標題:使用 C#簡單製作一個看門狗程式

原文連結:https://www.cnblogs.com/chonglu/p/16913746.html

首先謝謝網友的支持:

歡迎網友們投稿技術類文章,題材不限,沒有稿費的哈...

摘要

在有些特殊專案中,軟體可能是無人值守的,如果程式莫名其妙掛了或者處理序被幹掉了等等,這時開發一個看門狗程式是非常有必要的,它就像一隻打不死的小強,只要程式非正常退出,它就能立即再次將被看護的程式啟動起來。

程式碼實作

Tips:文末有完整原始碼,就不一步一步寫了

1、建立一個 Dog 類別,主要用於間隔性掃描被看護程式是否還在執行

開了個計時器,每 5 秒去檢查 1 次,如果沒有找到處理序則使用Process啟動程式

public class Dog
{
    private Timer timer = new Timer();
    private string processName ;
    private string filePath;//要監控的程式的路徑
    public Dog()
    {
        timer.Interval = 5000;
        timer.Tick += timer_Tick;
    }

    public void Start(string filePath)
    {
        this.filePath = filePath;
        this.processName = Path.GetFileNameWithoutExtension(filePath);
        timer.Enabled = true;
    }

    /// <summary>
    /// 定時檢測系統是否在執行
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void timer_Tick(object sender, EventArgs e)
    {
        try
        {
            Process[] myproc = Process.GetProcessesByName(processName);
            if (myproc.Length == 0)
            {
                Log.Info("檢測到看護程式已退出,開始重新啟用程式,程式路徑:{0}",filePath);
                ProcessStartInfo info = new ProcessStartInfo
                {
                    WorkingDirectory = Path.GetDirectoryName(filePath),
                    FileName = filePath,
                    UseShellExecute = true
                };
                Process.Start(info);
                Log.Info("看護程式已啟動");
            }
        }
        catch (Exception)
        {

        }

    }
}

2、在程式進入點接收被看護程式的路徑,啟動Dog掃描

static class Program
{
    static NotifyIcon icon = new NotifyIcon();
    private static Dog dog = new Dog();
    /// <summary>
    /// 應用程式的主進入點。
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
        if (args == null || args.Length == 0)
        {
            MessageBox.Show("啟動參數異常", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }

        string filePath = args[0];
        if(!File.Exists(filePath))
        {
            MessageBox.Show("啟動參數異常", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
        Process current = Process.GetCurrentProcess();
        Process[] processes = Process.GetProcessesByName(current.ProcessName);
        //遍歷與當前處理序名稱相同的處理序列表
        foreach (Process process in processes)
        {
            //如果實例已經存在則忽略當前處理序
            if (process.Id != current.Id)
            {
                //保證要開啟的處理序同已經存在的處理序來自同一檔案路徑
                if (process.MainModule.FileName.Equals(current.MainModule.FileName))
                {
                    //已經存在的處理序
                    return;
                }
                else
                {
                    process.Kill();
                    process.WaitForExit(3000);
                }
            }
        }
        icon.Text = "看門狗";
        icon.Visible = true;
        Log.Info("啟動看門狗,看護程式:{0}",filePath);
        dog.Start(filePath);
        Application.Run();
    }

}

3、簡單實作個日誌記錄器(使用第三方庫也行,建議看護程式最好不要有任何依賴),也可直接使用我下面這個,很簡單,無任何依賴

public class Log
{
    //讀寫鎖,當資源處於寫入模式時,其他執行緒寫入需要等待本次寫入結束之後才能繼續寫入
    private static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();
    //日誌檔案路徑
    public static string logPath = "logs\\dog.txt";

    //靜態方法todo:在處理話類型之前自動呼叫,去檢查日誌檔案是否存在
    static Log()
    {
        //建立資料夾
        if (!Directory.Exists("logs"))
        {
            Directory.CreateDirectory("logs");
        }
    }

    /// <summary>
    /// 寫入日誌.
    /// </summary>
    public static void Info(string format, params object[] args)
    {
        try
        {
            LogWriteLock.EnterWriteLock();
            string msg = args.Length > 0 ? string.Format(format, args) : format;
            using (FileStream stream = new FileStream(logPath, FileMode.Append))
            {
                StreamWriter write = new StreamWriter(stream);
                string content = String.Format("{0} {1}",DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),msg);
                write.WriteLine(content);
                //關閉並銷毀串流寫入檔案
                write.Close();
                write.Dispose();
            }
        }
        catch (Exception e)
        {

        }
        finally
        {
            LogWriteLock.ExitWriteLock();
        }
    }
}

至此,看護程式已經搞定。接著在主程式(被看護程式)封裝一個啟停類別

4、主程式封裝看門狗啟停類別

 public static class WatchDog
{
    private static string processName = "WatchDog";  //看護程式處理序名稱(注意這裡不是被看護程式名稱,你可以試一下換成主程式名稱會是什麼效果)
    private static string appPath = AppDomain.CurrentDomain.BaseDirectory;	//系統啟動目錄
    /// <summary>
    /// 啟動看門狗
    /// </summary>
    public static void Start()
    {
        try
        {
            string program = string.Format("{0}{1}.exe", appPath, processName);
            ProcessStartInfo info = new ProcessStartInfo
            {
                WorkingDirectory = appPath,
                FileName = program,
                CreateNoWindow = true,
                UseShellExecute = true,
                Arguments = Process.GetCurrentProcess().MainModule.FileName  //被看護程式的完整路徑
            };
            Process.Start(info);
        }
        catch (Exception)
        {
        }
    }

    /// <summary>
    /// 停用看門狗
    /// </summary>
    public static void Stop()
    {
        Process[] myproc = Process.GetProcessesByName(processName);
        foreach (Process pro in myproc)
        {
            pro.Kill();
            pro.WaitForExit(3000);
        }
    }
}

原理也很簡單,其中有兩點需要注意:

  • processName欄位表示看護程式,不是被看護程式,如果寫反了,嗯...(你可以試下效果)
  • Arguments參數是被看護程式的完整路徑,因為一般情況下,是由被看護程式啟動看護程式,所以我們可以直接使用Process.GetCurrentProcess().MainModule.FileName取得被看護程式的完整路徑

5、在主程式進入點啟動看門狗

public partial class App : Application
{
    [STAThread]
    static void Main()
    {
        //程式啟動前呼叫看護程式
        WatchDog.Start();
        Application app = new Application();
        MainWindow mainWindow = new MainWindow();
        app.Run(mainWindow);
    }
}

Winform、普通 WPF、Prism 等進入點都不太一樣,根據專案實際情況靈活處理即可

最後在需要正常退出程式的地方(也就是主程式關閉按鈕或其他想要正常退出程式的地方)停止看門狗程式

效果

原始碼

https://github.com/luchong0813/WatchDogDemo

繼續探索

延伸閱讀

更多文章
同分類 / 同標籤 2026/2/7

AOT使用經驗總結

從專案建立伊始,就應養成良好的習慣,即只要添加了新功能或使用了較新的語法,就及時進行 AOT 發布測試。

繼續閱讀