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

awj*_*awj 5 c# console asynchronous console-application cancellation

我正在开发一个小型实用程序控制台应用程序,内置于 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
    }
}
Run Code Online (Sandbox Code Playgroud)

在运行(可选)长时间运行的进程的方法中有检查这个全局_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 = // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,每当我按下 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;
                                  };
Run Code Online (Sandbox Code Playgroud)

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

GFo*_*y83 5

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

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

namespace ConsoleApp
{
    class Program
    {
        // Cancellation Tokens - https://docs.microsoft.com/en-us/previous-versions/dd997289(v=vs.110)
        private static readonly CancellationTokenSource canToken = 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");
                canToken.Cancel();
                eventArgs.Cancel = true;
            };

            await Worker();

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

        async static Task Worker()
        {
            while (!canToken.IsCancellationRequested)
            {
                // do work       
                Console.WriteLine("Worker is working");
                await Task.Delay(1000); // arbitrary delay
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)