Simple Creation of a Watchdog Program Using C#

Simple Creation of a Watchdog Program Using C#

In some special projects, the software may be unattended. If the program crashes for no reason or the process is killed, it is very necessary to develop a watchdog program.

Last updated 11/11/2022 11:20 AM
傲慢与偏见
6 min read
Category
.NET
Tags
.NET C#

This article is contributed by a reader.

Author: Pride and Prejudice

Original title: Using C# to simply create a watchdog program

Original link: https://www.cnblogs.com/chonglu/p/16913746.html

First, thank you for the reader's support:

We welcome readers to submit technical articles, no topic restrictions, but there is no payment...

Summary

In some special projects, the software may be unattended. If the program unexpectedly crashes or the process is killed, etc., developing a watchdog program is very necessary. It is like an indestructible pest; as long as the program exits abnormally, it can immediately restart the guarded program.

Code Implementation

Tips: The complete source code is at the end of the article, so I won't write it step by step here.

  1. Create a Dog class, mainly used to periodically scan whether the guarded program is still running.

A timer is opened to check every 5 seconds. If no process is found, use Process to start the program.

public class Dog
{
    private Timer timer = new Timer();
    private string processName ;
    private string filePath;// Path of the program to monitor
    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>
    /// Periodically check if the system is running
    /// </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("Detected that the guarded program has exited, starting to reactivate the program, program path:{0}",filePath);
                ProcessStartInfo info = new ProcessStartInfo
                {
                    WorkingDirectory = Path.GetDirectoryName(filePath),
                    FileName = filePath,
                    UseShellExecute = true
                };
                Process.Start(info);
                Log.Info("Guarded program started");
            }
        }
        catch (Exception)
        {

        }

    }
}
  1. At the program entry, receive the path of the guarded program and start the Dog scanning.
static class Program
{
    static NotifyIcon icon = new NotifyIcon();
    private static Dog dog = new Dog();
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
        if (args == null || args.Length == 0)
        {
            MessageBox.Show("Startup parameter error", "Prompt", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }

        string filePath = args[0];
        if(!File.Exists(filePath))
        {
            MessageBox.Show("Startup parameter error", "Prompt", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
        Process current = Process.GetCurrentProcess();
        Process[] processes = Process.GetProcessesByName(current.ProcessName);
        // Iterate through the list of processes with the same name as the current process
        foreach (Process process in processes)
        {
            // If an instance already exists, ignore the current process
            if (process.Id != current.Id)
            {
                // Ensure the process to be opened comes from the same file path as the existing process
                if (process.MainModule.FileName.Equals(current.MainModule.FileName))
                {
                    // Existing process
                    return;
                }
                else
                {
                    process.Kill();
                    process.WaitForExit(3000);
                }
            }
        }
        icon.Text = "Watchdog";
        icon.Visible = true;
        Log.Info("Starting watchdog, guarded program:{0}",filePath);
        dog.Start(filePath);
        Application.Run();
    }

}
  1. Simply implement a logger (a third-party library can also be used, but it is recommended that the watchdog program have no dependencies). You can also directly use the following one, which is very simple and has no dependencies.
public class Log
{
    // Reader-writer lock: when a resource is in write mode, other threads must wait until the current write finishes
    private static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();
    // Log file path
    public static string logPath = "logs\\dog.txt";

    // Static constructor: automatically called before processing any type, to check if the log file exists
    static Log()
    {
        // Create folder
        if (!Directory.Exists("logs"))
        {
            Directory.CreateDirectory("logs");
        }
    }

    /// <summary>
    /// Write log.
    /// </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);
                // Close and dispose the stream to write to file
                write.Close();
                write.Dispose();
            }
        }
        catch (Exception e)
        {

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

At this point, the watchdog program is complete. Next, encapsulate a start/stop class in the main program (the guarded program).

  1. Main program encapsulates watchdog start/stop class.
 public static class WatchDog
{
    private static string processName = "WatchDog";  // Watchdog process name (note: this is not the guarded program name; try replacing it with the main program name to see the effect)
    private static string appPath = AppDomain.CurrentDomain.BaseDirectory;	// System startup directory
    /// <summary>
    /// Start watchdog
    /// </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  // Full path of the guarded program
            };
            Process.Start(info);
        }
        catch (Exception)
        {
        }
    }

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

The principle is also simple, with two points to note:

  • The processName field represents the watchdog program, not the guarded program. If you get it wrong, well... (you can try the effect)
  • The Arguments parameter is the full path of the guarded program, because generally the guarded program starts the watchdog program. So we can directly use Process.GetCurrentProcess().MainModule.FileName to obtain the full path of the guarded program.
  1. Start the watchdog at the main program entry point.
public partial class App : Application
{
    [STAThread]
    static void Main()
    {
        // Call the watchdog program before the program starts
        WatchDog.Start();
        Application app = new Application();
        MainWindow mainWindow = new MainWindow();
        app.Run(mainWindow);
    }
}

Entry points for Winform, regular WPF, Prism, etc. are different; handle flexibly according to the actual project situation.

Finally, stop the watchdog program where the main program needs to exit normally (i.e., the main program's close button or other places where a normal exit is desired).

Effect

Source Code

https://github.com/luchong0813/WatchDogDemo

Keep Exploring

Related Reading

More Articles