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. 重要注意事项
- 清理时间限制:Windows在进程终止时只给大约30秒的时间进行清理
- 避免阻塞操作:清理操作应该快速完成
- 幂等性设计:清理操作应该可以安全地重复执行
- 关键数据立即持久化:重要数据应该立即写入磁盘,而不是缓存在内存中
推荐的最佳实践组合:
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%保证在强制终止时执行清理代码。最好的策略是:
- 设计应用程序使其可以容忍突然终止
- 定期保存状态到持久化存储
- 提供崩溃恢复机制
- 使用事务性操作保证数据一致性