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

时间:2018-01-12 09:06:06

标签: c# asynchronous console 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
    }
}

在运行(可选)长时间运行进程的方法中,有一个逻辑可以检查这个全局_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并将其用作退出长时间运行的进程的方法而不用退出控制台应用程序本身。这甚至可能吗?

1 个答案:

答案 0 :(得分:0)

使用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
            }
        }
    }
}