A cross-platform .NET library that ensures child processes automatically terminate when the parent process exits unexpectedly.
- Cross-Platform Support: Works on Windows, Linux, and macOS
- Automatic Cleanup: Child processes terminate when the parent exits
- Windows Job Objects: Uses Job Objects for kernel-level process management on Windows
- Process Tree Termination: Terminates all descendant processes via native APIs
- Async/Await Support: Full asynchronous API with cancellation tokens
- Process Monitoring: Real-time statistics and lifecycle events
- Batch Processing: Start multiple processes with concurrency control
- Thread-Safe: Supports concurrent operations
- Graceful Shutdown: Configurable timeout and fallback mechanisms
- Custom Logging: Pluggable log action delegate
- .NET Standard 2.0+ / .NET Framework 4.6.1+ / .NET Core 2.0+ / .NET 5+
- .NET 10.0+
| Target Framework | Status |
|---|---|
netstandard2.0 |
Supported |
netstandard2.1 |
Supported |
net10.0 |
Supported |
Install-Package ChildProcessGuarddotnet add package ChildProcessGuardusing ChildProcessGuard;
// Simple usage with automatic cleanup
using var guardian = new ProcessGuardian();
var process = guardian.StartProcess("notepad.exe");
Console.WriteLine($"Started process with PID: {process.Id}");
// Process will be automatically terminated when guardian is disposedusing ChildProcessGuard;
// Configure with builder pattern
using var guardian = ProcessGuardianBuilder.Debug()
.WithKillTimeout(TimeSpan.FromSeconds(10))
.WithMaxProcesses(50)
.WithDetailedLogging(true)
.WithAutoCleanup(true, TimeSpan.FromMinutes(1))
.Build();
// Set up event handlers
guardian.ProcessError += (sender, e) =>
Console.WriteLine($"Error: {e.Operation} - {e.Exception.Message}");
guardian.ProcessLifecycleEvent += (sender, e) =>
Console.WriteLine($"Event: {e.EventType} - {e.ProcessInfo}");
var process = guardian.StartProcess("myapp.exe", "--verbose");using var guardian = new ProcessGuardian();
var envVars = new Dictionary<string, string>
{
{ "DEBUG", "true" },
{ "CONFIG_PATH", "/etc/myapp/config.json" },
{ "LOG_LEVEL", "verbose" }
};
var process = guardian.StartProcess(
"myapp.exe",
"--config config.json",
workingDirectory: "/path/to/working/dir",
environmentVariables: envVars
);using var guardian = new ProcessGuardian();
var startInfo = new ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = "-NoProfile -Command \"Get-Process\"",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};
var process = guardian.StartProcessWithStartInfo(startInfo);
string output = process.StandardOutput.ReadToEnd();using var guardian = ProcessGuardianBuilder.HighPerformance().Build();
// Prepare multiple processes
var processInfos = Enumerable.Range(1, 5)
.Select(i => new ProcessStartInfo("ping", "127.0.0.1 -n 3"))
.ToList();
// Start all processes concurrently
var processes = await guardian.StartProcessesBatchAsync(processInfos, maxConcurrency: 3);
// Wait for all to complete
bool allCompleted = await guardian.WaitForAllProcessesAsync(TimeSpan.FromSeconds(30));
Console.WriteLine($"All processes completed: {allCompleted}");using var guardian = new ProcessGuardian();
// Start processes
guardian.StartProcess("notepad.exe");
guardian.StartProcess("calc.exe");
// Get statistics
var stats = guardian.GetStatistics();
Console.WriteLine($"Total: {stats.TotalProcesses}, Running: {stats.RunningProcesses}");
Console.WriteLine($"Memory Usage: {stats.TotalMemoryUsage / 1024 / 1024:F1} MB");
// Get detailed process information
var runningProcesses = guardian.GetProcessesByStatus(ProcessStatus.Running);
foreach (var processInfo in runningProcesses)
{
Console.WriteLine($"Process: {processInfo}");
Console.WriteLine($"Runtime: {processInfo.GetRuntime():hh\\:mm\\:ss}");
}using var guardian = new ProcessGuardian();
var cts = new CancellationTokenSource();
// Start process asynchronously
var process = await guardian.StartProcessAsync("myapp.exe", cancellationToken: cts.Token);
// Terminate all processes
int terminated = await guardian.KillAllProcessesAsync(TimeSpan.FromSeconds(10));
Console.WriteLine($"Terminated {terminated} processes");
// Selective termination
int killed = await guardian.TerminateProcessesWhere(
p => p.GetRuntime() > TimeSpan.FromMinutes(5),
TimeSpan.FromSeconds(5)
);// Route logs to your logging framework
using var guardian = new ProcessGuardianBuilder()
.WithDetailedLogging(true)
.WithLogAction(message => logger.LogInformation(message))
.Build();
// Or use options directly
var options = new ProcessGuardianOptions
{
EnableDetailedLogging = true,
LogAction = message => Debug.WriteLine(message)
};
using var guardian = new ProcessGuardian(options);using var guardian = new ProcessGuardian();
string executable, arguments;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
executable = "cmd.exe";
arguments = "/c echo Hello from Windows";
}
else
{
executable = "/bin/bash";
arguments = "-c 'echo Hello from Unix'";
}
var process = guardian.StartProcess(executable, arguments);
await process.WaitForExitAsync();var options = new ProcessGuardianOptions
{
ProcessKillTimeout = TimeSpan.FromSeconds(30), // Graceful termination timeout
EnableDetailedLogging = false, // Verbose logging
ForceKillOnTimeout = true, // Force kill if timeout exceeded
MaxManagedProcesses = 100, // Maximum concurrent processes
AutoCleanupDisposedProcesses = true, // Auto cleanup exited processes
CleanupInterval = TimeSpan.FromMinutes(5), // Cleanup check interval
ThrowOnProcessOperationFailure = false, // Exception handling behavior
LogAction = msg => Console.WriteLine(msg) // Custom log handler (optional)
};
using var guardian = new ProcessGuardian(options);// High performance configuration
using var highPerf = ProcessGuardianBuilder.HighPerformance().Build();
// Debug configuration with detailed logging
using var debug = ProcessGuardianBuilder.Debug().Build();
// Custom configuration
using var custom = new ProcessGuardianBuilder()
.WithKillTimeout(TimeSpan.FromSeconds(15))
.WithMaxProcesses(200)
.WithDetailedLogging(true)
.WithLogAction(msg => myLogger.Log(msg))
.Build();- Uses Windows Job Objects with
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEflag - Automatically terminates all child processes when the job handle is closed
- Kernel-level guarantee for process cleanup
- Per-process Job Object assignment tracking with graceful fallback
- Uses manual process tree tracking via
/procfilesystem (Linux) and native APIs - Uses
SIGTERMfor graceful termination,SIGKILLfor force termination - Enumerates and terminates descendant processes using process tree walking
- Hooks into
AppDomain.ProcessExitandConsoleCancelKeyPressevents - Falls back to basic process termination if advanced features fail
- Provides .NET 5+ features (e.g.,
WaitForExitAsync) for .NET Standard 2.0/2.1 compatibility
- Always use
usingstatements or callDispose()explicitly - Configure appropriate timeouts based on process characteristics
- Handle events for production applications to track errors
- Use builder pattern for complex configurations
- Use
LogActionto route logs to your logging framework instead of relying onConsole.WriteLine - Monitor statistics in long-running applications
- Test cross-platform behavior when targeting multiple operating systems
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
For issues, questions, or suggestions, please open an issue on GitHub.