捕获 CancelKeyPress 以在安全点停止异步控制台应用程序

问题描述 投票:0回答:1

我正在开发一个小型实用程序控制台应用程序,内置于 C# 7.1(具有

async Main
支持)。

该应用程序采用多个输入命令之一,然后启动一个长时间运行的进程,该进程迭代数以万计的项目,处理每个项目。

我希望能够随时取消此过程(使用 CTRL+C),尽管程序不应该立即取消,而是应该完成当前迭代,然后停止。

这是我迄今为止所拥有的内容的简化版本。

private static bool _cancel;    

private static async Task Main(string[] args)
{
    Console.CancelKeyPress += (sender, eventArgs) =>
                                      {
                                          eventArgs.Cancel = true;                                              _logger("Migration will be stopped after the current record has been completed.\n");
                                          _cancel = true;
                                      };

    while (!_cancel)
    {
        var input = Console.ReadLine();

        // handle the various input commands
    }
}

在运行(可选)长时间运行进程的方法内部,有一个检查此全局

_cancel
变量的逻辑:

private static async Task RunPersonMigration(Order order)
{
    var nextPerson = // ...
    while (nextPerson.IsValid)
    {
        // business logic

        if (_cancel)
        {
            _logger("Person migration stopped by user.\n");
            return;
        }

        nextPerson = // ...
    }
}

但是,每当我按下 CTRL+C 时,Visual Studio 调试器都会要求我查找程序集,并且每次通常都是不同的程序集。例如,我被要求找到 waithandle.cs 和 thread.cs。因为我无法找到此类文件,所以正在运行的调试进程突然停止。

我永远无法看出哪一行导致了问题,并且无论多少断点都无济于事。

基本上,我尝试使用 CTRL+C 退出长时间运行的进程而不退出控制台应用程序。

谁能告诉我应该如何正确处理在我选择的时间点取消长时间运行的控制台进程?

更新:
如果我更新我的 CancelKeyPress 委托...

Console.CancelKeyPress += (sender, eventArgs) =>
                                  {
                                      **eventArgs.Cancel = true;**                                              _logger("Migration will be stopped after the current record has been completed.\n");
                                      _cancel = true;
                                  };

然后这会阻止程序崩溃并关闭,但我仍然希望能够捕获 CTRL+C 并将其用作退出长时间运行的进程的方法,而不需要退出控制台应用程序本身。这可能吗?

c# asynchronous console console-application cancellation
1个回答
13
投票
CancellationToken

的工作示例,可以向下传递到较低级别:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp
{
    class Program
    {
        // https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource
        private static readonly CancellationTokenSource cts = new CancellationTokenSource();

        static async Task Main(string[] args)
        {
            Console.WriteLine("Application has started. Ctrl-C to end");

            Console.CancelKeyPress += (sender, eventArgs) =>
            {
                Console.WriteLine("Cancel event triggered");
                cts.Cancel();
                eventArgs.Cancel = true;
            };

            await Worker(cts.Token);

            Console.WriteLine("Now shutting down");
            await Task.Delay(1000);
        }

        async static Task Worker(CancellationToken ct)
        {
            while (!ct.IsCancellationRequested) // or optionally use cts.IsCancellationRequested field directly
            {
                // do work       
                Console.WriteLine("Worker is working");
                await Task.Delay(1000); // arbitrary delay
            }
        }

    }
}

© www.soinside.com 2019 - 2024. All rights reserved.