win下,当.NET控制台进程被强制终止(如关闭控制台、任务管理器结束进程等)时,如何优雅地清理数据

win下,当.NET控制台进程被强制终止(如关闭控制台、任务管理器结束进程等)时,如何优雅地清理数据

在Windows下,当.NET控制台进程被强制终止时,优雅地清理数据是一个挑战。以下是几种处理方案,按推荐程度排序:

1. 最佳方案:结合多种事件处理

csharp 复制代码
using System;
using System.Runtime.InteropServices;
using System.Threading;

class Program
{
    // Windows API 定义
    [DllImport("Kernel32")]
    private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);
    
    private delegate bool EventHandler(CtrlType sig);
    private static EventHandler _handler;
    
    enum CtrlType
    {
        CTRL_C_EVENT = 0,
        CTRL_BREAK_EVENT = 1,
        CTRL_CLOSE_EVENT = 2,
        CTRL_LOGOFF_EVENT = 5,
        CTRL_SHUTDOWN_EVENT = 6
    }
    
    private static bool ConsoleCtrlCheck(CtrlType ctrlType)
    {
        Console.WriteLine($"接收到关闭信号: {ctrlType}");
        Cleanup();
        return true; // 返回true表示已处理
    }
    
    static void Main(string[] args)
    {
        // 注册控制台控制事件处理器
        _handler += new EventHandler(ConsoleCtrlCheck);
        SetConsoleCtrlHandler(_handler, true);
        
        // 注册进程退出事件
        AppDomain.CurrentDomain.ProcessExit += (s, e) => Cleanup();
        
        // 注册AppDomain卸载事件
        AppDomain.CurrentDomain.DomainUnload += (s, e) => Cleanup();
        
        // 对于需要取消的循环操作,使用CancellationToken
        var cts = new CancellationTokenSource();
        Console.CancelKeyPress += (sender, e) =>
        {
            Console.WriteLine("Ctrl+C 被按下");
            e.Cancel = true; // 防止立即退出
            cts.Cancel();
            Cleanup();
        };
        
        try
        {
            // 主程序逻辑
            RunApplication(cts.Token);
        }
        finally
        {
            // 正常退出时的清理
            Cleanup();
        }
    }
    
    static void RunApplication(CancellationToken token)
    {
        // 示例:使用CancellationToken来响应取消请求
        while (!token.IsCancellationRequested)
        {
            Console.WriteLine("程序运行中... 按Ctrl+C退出");
            Thread.Sleep(1000);
        }
    }
    
    static void Cleanup()
    {
        // 使用标志避免重复清理
        if (_isCleaned) return;
        _isCleaned = true;
        
        try
        {
            Console.WriteLine("开始清理资源...");
            
            // 1. 保存数据到文件
            SaveCriticalData();
            
            // 2. 关闭数据库连接等资源
            CloseDatabaseConnections();
            
            // 3. 释放文件锁等
            ReleaseFileLocks();
            
            // 4. 记录日志
            LogShutdown();
            
            Console.WriteLine("清理完成");
            
            // 等待所有异步操作完成
            Thread.Sleep(500); // 给清理操作一点时间
        }
        catch (Exception ex)
        {
            Console.WriteLine($"清理过程中发生错误: {ex.Message}");
        }
    }
    
    private static bool _isCleaned = false;
    
    // 示例清理方法
    static void SaveCriticalData()
    {
        try
        {
            // 保存关键数据到文件
            File.WriteAllText("backup.txt", DateTime.Now.ToString());
        }
        catch { /* 忽略错误 */ }
    }
    
    static void CloseDatabaseConnections()
    {
        // 关闭数据库连接
    }
    
    static void ReleaseFileLocks()
    {
        // 释放文件锁
    }
    
    static void LogShutdown()
    {
        // 记录关闭日志
    }
}

2. 备用方案:使用子进程监控

对于无法捕获的终止(如任务管理器强制结束),可以创建一个监控进程:

csharp 复制代码
// 监控程序(父进程)
class MonitorProgram
{
    static void Main()
    {
        var process = new Process
        {
            StartInfo = new ProcessStartInfo
            {
                FileName = "YourConsoleApp.exe",
                UseShellExecute = false
            }
        };
        
        process.Start();
        
        // 监控子进程退出
        process.WaitForExit();
        
        // 子进程退出后执行清理
        if (process.ExitCode != 0)
        {
            PerformEmergencyCleanup();
        }
    }
    
    static void PerformEmergencyCleanup()
    {
        // 紧急清理逻辑
    }
}

3. 防御性编程策略

3.1 定期保存状态

csharp 复制代码
class ProgramWithAutoSave
{
    private static Timer _autoSaveTimer;
    private static readonly object _saveLock = new object();
    
    static void Main()
    {
        // 每隔5分钟自动保存
        _autoSaveTimer = new Timer(_ =>
        {
            lock (_saveLock)
            {
                SaveCurrentState();
            }
        }, null, TimeSpan.Zero, TimeSpan.FromMinutes(5));
        
        // 主程序逻辑
    }
    
    static void SaveCurrentState()
    {
        // 保存当前状态到临时文件
        var tempFile = $"autosave_{DateTime.Now:yyyyMMddHHmmss}.tmp";
        // ... 保存逻辑
    }
}

3.2 使用文件锁确保数据一致性

csharp 复制代码
class ProgramWithFileLock
{
    private static FileStream _lockFile;
    
    static void Main()
    {
        try
        {
            // 创建锁文件
            _lockFile = File.Open("app.lock", 
                FileMode.OpenOrCreate, 
                FileAccess.ReadWrite, 
                FileShare.None);
            
            // 程序主逻辑
            RunProgram();
        }
        finally
        {
            _lockFile?.Close();
            File.Delete("app.lock");
        }
    }
    
    static void CheckForPreviousCrash()
    {
        if (File.Exists("app.lock"))
        {
            // 上次程序异常退出
            RecoverFromCrash();
        }
    }
}

4. Windows服务替代方案

如果可靠性要求很高,考虑使用Windows服务:

csharp 复制代码
using System.ServiceProcess;

public class MyService : ServiceBase
{
    protected override void OnStart(string[] args)
    {
        // 启动逻辑
    }
    
    protected override void OnStop()
    {
        // 优雅停止
        Cleanup();
    }
    
    protected override void OnShutdown()
    {
        // 系统关机时调用
        Cleanup();
        base.OnShutdown();
    }
}

5. 重要注意事项

  1. 清理时间限制:Windows在进程终止时只给大约30秒的时间进行清理
  2. 避免阻塞操作:清理操作应该快速完成
  3. 幂等性设计:清理操作应该可以安全地重复执行
  4. 关键数据立即持久化:重要数据应该立即写入磁盘,而不是缓存在内存中

推荐的最佳实践组合:

csharp 复制代码
// 简化的最佳实践示例
class RobustConsoleApp
{
    static void Main()
    {
        // 1. 设置控制台事件处理器
        SetConsoleHandlers();
        
        // 2. 检查上次是否异常退出
        CheckForCrashRecovery();
        
        // 3. 使用using确保资源释放
        using (var criticalResource = new CriticalResource())
        using (var cts = new CancellationTokenSource())
        {
            // 4. 设置取消令牌
            Console.CancelKeyPress += (s, e) =>
            {
                e.Cancel = true;
                cts.Cancel();
            };
            
            // 5. 主程序循环
            try
            {
                MainLoop(cts.Token);
            }
            finally
            {
                Cleanup();
            }
        }
    }
}

重要提示:没有任何方法可以100%保证在强制终止时执行清理代码。最好的策略是:

  1. 设计应用程序使其可以容忍突然终止
  2. 定期保存状态到持久化存储
  3. 提供崩溃恢复机制
  4. 使用事务性操作保证数据一致性
相关推荐
向上的车轮2 小时前
为什么.NET(C#)转 Java 开发时常常在“吐槽”Java:checked exception
java·c#·.net
波波0074 小时前
每日一题:.NET 的 GC是如何分代工作的?
算法·.net·gc
波波0071 天前
每日一题:中间件是如何工作的?
中间件·.net·面试题
无风听海1 天前
.NET 10之可空引用类型
数据结构·.net
码云数智-园园1 天前
基于 JSON 配置的 .NET 桌面应用自动更新实现指南
.net
无风听海1 天前
.NET 10 之dotnet run的功能
.net
岩屿1 天前
Ubuntu下安装Docker并部署.NET API(二)
运维·docker·容器·.net
码云数智-大飞1 天前
.NET 中高效实现 List 集合去重的多种方法详解
.net
easyboot1 天前
使用tinyply.net保存ply格式点云
.net
张人玉1 天前
WPF 多语言实现完整笔记(.NET 4.7.2)
笔记·.net·wpf·多语言实现·多语言适配