You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何捕获CancelKeyPress在安全点停止异步C#控制台应用?

解决方案:实现CTRL+C协作式取消长时任务且不退出应用

完全可以实现你想要的效果——按下CTRL+C后让长时任务完成当前迭代再停止,同时保持控制台应用继续运行接收新指令。你的方向是对的,eventArgs.Cancel = true确实能阻止系统直接终止进程,接下来我们用更规范的异步取消机制替换全局变量,同时解决调试器的问题。

核心改进点:用CancellationTokenSource替代全局_cancel变量

全局变量在异步场景下容易出现线程安全问题,而.NET提供的CancellationTokenSource和CancellationToken是专门为协作式取消设计的,更可靠也更符合异步编程最佳实践。

完整实现代码

private static CancellationTokenSource _cts;
private static readonly ILogger _logger = // 初始化你的日志实例

private static async Task Main(string[] args)
{
    // 初始化取消令牌源
    _cts = new CancellationTokenSource();

    // 注册CTRL+C事件处理
    Console.CancelKeyPress += OnCancelKeyPress;

    try
    {
        while (true)
        {
            Console.WriteLine("输入指令启动迁移任务(输入start开始,输入quit退出):");
            var input = Console.ReadLine();
            
            switch (input?.Trim().ToLower())
            {
                case "start":
                    // 启动迁移任务,传入取消令牌
                    _ = RunPersonMigration(/* 你的Order实例 */, _cts.Token);
                    break;
                case "quit":
                    Console.WriteLine("正在退出应用...");
                    _cts.Cancel();
                    return;
                default:
                    Console.WriteLine("无效指令,请重新输入");
                    break;
            }
        }
    }
    finally
    {
        Console.CancelKeyPress -= OnCancelKeyPress;
        _cts.Dispose();
    }
}

private static void OnCancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
    // 阻止系统终止进程
    e.Cancel = true;
    
    if (!_cts.IsCancellationRequested)
    {
        _logger.LogInformation("迁移任务将在完成当前记录后停止...");
        // 触发取消信号
        _cts.Cancel();
    }
}

private static async Task RunPersonMigration(Order order, CancellationToken cancellationToken)
{
    var nextPerson = // 获取下一个待处理的Person
    while (nextPerson.IsValid)
    {
        // 执行当前记录的业务逻辑(这里要确保是同步或异步但可中断的操作)
        await ProcessPersonAsync(nextPerson);

        // 检查取消信号——如果已请求取消,完成当前迭代后退出循环
        if (cancellationToken.IsCancellationRequested)
        {
            _logger.LogInformation("用户已取消迁移任务,已完成当前记录处理");
            break;
        }

        nextPerson = // 获取下一个待处理的Person
    }

    // 任务结束后重置取消令牌源,以便后续可以再次启动任务
    _cts.Dispose();
    _cts = new CancellationTokenSource();
}

// 模拟业务处理方法
private static async Task ProcessPersonAsync(Person person)
{
    // 模拟耗时操作
    await Task.Delay(1000);
    _logger.LogInformation($"已处理Person: {person.Id}");
}

关键细节说明

  • 协作式取消:cancellationToken.IsCancellationRequested只会检查是否有取消请求,不会强制终止任务,确保当前迭代的业务逻辑完成后再退出循环。
  • 重置取消令牌源:任务停止后重新创建CancellationTokenSource,这样用户可以再次输入start启动新的迁移任务。
  • 事件注册与释放:在finally块中取消事件注册并释放CancellationTokenSource,避免资源泄漏。

解决调试器弹出系统文件的问题

你遇到的调试器要求定位waithandle.cs、thread.cs等文件,是因为Visual Studio默认启用了“.NET框架源代码调试”。可以按以下步骤关闭:

  • 打开Visual Studio的工具 → 选项 → 调试 → 常规
  • 取消勾选“启用.NET框架源代码调试”选项
  • 点击确定后重启调试,就不会再弹出这些窗口了

这样调整后,按下CTRL+C时,系统不会终止进程,迁移任务会完成当前记录后停止,控制台应用依然保持运行,可以接收新的指令。

内容的提问来源于stack exchange,提问作者awj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:00:46