Поддерживать консольное приложение.NET до завершения последовательности завершения

Я работаю над приложением для сбора данных и хочу, чтобы оно корректно завершилось. То есть он обрабатывает все уже собранные данные, сбрасывает все (файловые) буферы на "диск" (постоянную память) и может даже загружать данные в облако.

Итак, я написал (основываясь на этом ответе) код ниже, чтобы перехватывать каждое закрытое событие. (Это всего лишь тестовый код.)

Проблема: если я использую X в правом верхнем углу консоли, программа завершается после небольшой задержки, даже если последовательность завершения все еще выполняется. (Обработчик действительно вызывается, и он начинает ждать присоединения потоков, но затем через некоторое время его убивают.) Если я завершаюсь с помощью Crt+C или Ctr+Break, он работает как положено; Последовательность завершения завершает и завершает процесс.

Вопрос: Как я могу заставить ОС ждать завершения моего приложения вместо того, чтобы завершить его после короткого льготного периода?

#region Trap application termination
[DllImport("Kernel32")]
private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);

private delegate bool EventHandler(CtrlType sig);
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 Handler(CtrlType sig, List<Thread> threads, List<Task> tasks, CancellationTokenSource cancellationRequest)
{
    //starts new foregeound thread, so the process doesn't terminate when all the cancelled threads end
    Thread closer = new Thread(() => terminationSequence(threads, tasks, cancellationRequest));
    closer.IsBackground = false;
    closer.Start();

    closer.Join();  //wait for the termination sequence to finish

    return true; //just to be pretty; this never runs (obviously)
}
private static void terminationSequence(List<Thread> threads, List<Task> tasks, CancellationTokenSource cancellationRequest)
{
    cancellationRequest.Cancel(); //sends cancellation requests to all threads and tasks

    //wait for all the tasks to meet the cancellation request
    foreach (Task task in tasks)
    {
        task.Wait();
    }

    //wait for all the treads to meet the cancellation request
    foreach (Thread thread in threads)
    {
        thread.Join();
    }
    /*maybe do some additional work*/
    //simulate work being done
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    Console.WriteLine("Spinning");
    while (stopwatch.Elapsed.Seconds < 30)
    {
        if (stopwatch.Elapsed.Seconds % 2 == 0)
        {
            Console.Clear();
            Console.WriteLine("Elapsed Time: {0}m {1}s", stopwatch.Elapsed.Minutes, stopwatch.Elapsed.Seconds);
        }
        Thread.SpinWait(10000);
    }

    Environment.Exit(0); //exit the process
}
#endregion

static void Main(string[] args)
{
    CancellationTokenSource cancellationRequest = new CancellationTokenSource();    //cancellation signal to all threads and tasks
    List<Thread> threads = new List<Thread>(); //list of threads

    //specifys termination handler
    _handler += new EventHandler((type) => Handler(type, threads, new List<Task>(), cancellationRequest));
    SetConsoleCtrlHandler(_handler, true);

    //creating a new thread
    Thread t = new Thread(() => logic(cancellationRequest.Token));
    threads.Add(t);
    t.Start();
}

1 ответ

Начиная с C# 7.1 вы можете иметь async Task Main() метод. Используя это, вы можете изменить ваш обработчик, чтобы создать метод, и ждать его в Main метод.

Sidenote: вы должны использовать задачи, а не темы, где вы можете. Задачи лучше управляют вашей веткой, и они запускаются из ThreadPool. Когда вы создаете новый экземпляр Thread, предполагается, что это будет длительная задача, и окна будут обрабатывать ее по-разному.

Так что с этим, подумайте об упаковке TerminateSequence метод в задаче, а не поток, и сделать эту задачу членом вашего класса. Теперь вам не придется ждать его в контексте обработчика, вместо этого вы можете ждать его в Main метод.

Оставшаяся часть кода остается прежней, вы можете сделать следующее:

private Task _finalTask;

private static bool Handler(CtrlType sig, List<Thread> threads, List<Task> tasks, CancellationTokenSource cancellationRequest)
{
    //starts new foregeound thread, so the process doesn't terminate when all the cancelled threads end
    _finalTask = Task.Run(() => terminationSequence(threads, tasks, cancellationRequest));
}

// ...

static async Task Main(string[] args)
{
    // ...

    // Wait for the termination process
    if(_finalProcess != null)
        await _finalTask
}

Если вы не работаете с C# 7.1, вы все равно можете сделать это, это будет немного менее элегантно. Все, что вам нужно сделать, это подождать:

_finalTask?.Wait();

И это должно сделать это.

Другие вопросы по тегам