Как что-то делать, когда консоль закрыта пользователем (С++)?

У меня есть программа, запущенная на консоли, и мне нужно сохранить прогресс, когда пользователь закрывает консоль. Как это сделать?

Пример:

void func()
{
    while (true)
        if (UserClosesTheConsole()) // Dunno how to detect
            SaveProgess();
}

int main()
{
    std::thread t(func);
}

Любое решение (не обязательно кроссплатформенное) приветствуется.

Вы работаете на MSWindows?

drescherjm 29.07.2024 03:06

Да, я работаю на MSWindows и использую MSVC в качестве компилятора и Visual Studio в качестве среды.

RandomGuy 29.07.2024 03:07
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
79
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я бы сделал что-то вроде этого, поместив всю бизнес-логику внутри класса. Этот класс также отвечает за управление временем жизни основного цикла (потока). Я также использую условные переменные, чтобы обеспечить возможность оперативного завершения основного цикла.

#include <cassert>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

// It is always a good idea to encapsulate the business logic in a class
// this way you can control the lifetime of the business logic
// and the runtime state of it. This is especially important when
// you have background threads running that you need to stop gracefully.
class BusinessLogic
{
public:
    BusinessLogic() = default;

    ~BusinessLogic()
    {
        Stop();
    }

    // There is a chance for a small race condition where starting a thread
    // will return before the background thread is actually scheduled.
    // So wait for the mainloop to have really started (thread scheduled) before continuing.
    void Run()
    {
        m_thread = std::thread{ [this] { Mainloop(); } };
        WaitForState(State::Running); // Wait for mainloop to have really started
    }

    // Stop the business logic
    // this can happen because main asks it to stop or the signal handler asks it to stop
    void Stop()
    {
        // if already stopped do nothing
        {
            std::unique_lock<std::mutex> lock{ m_mutex };
            if (m_state == State::Stopped)
                return;
        }
        SetState(State::Stopping);
        WaitForState(State::Stopped); // Wait for mainloop to have really stopped
    }

    void SaveProgress()
    {
        std::cout << "Saving progress\n";
    }

private:
    // state of the business logic/mainloop
    enum class State
    {
        Initial,
        Starting,
        Running,
        Stopping,
        Stopped
    };

    // use a condition variable to signal state changes
    void SetState(State state)
    {
        std::unique_lock<std::mutex> lock{ m_mutex };
        m_state = state;
        m_cv.notify_all();
    }

    // use the condition varibale to wait for state changes
    void WaitForState(State state)
    {
        std::unique_lock<std::mutex> lock{ m_mutex };
        m_cv.wait(lock, [this, state] { return m_state == state; });
    }

    void Mainloop()
    {
        SetState(State::Running);
        while(true)
        {
            std::cout << "." << std::flush; // show mainloop is still running

            // Using a condition variable with a timeout allows for a clean and responsive shutdown
            std::unique_lock<std::mutex> lock{ m_mutex };
            m_cv.wait_for(lock, std::chrono::seconds(1), [this] { return m_state == State::Stopping; });

            // Check if we should stop (cooperative shutdown of the thread)
            if (m_state == State::Stopping)
            {
                SaveProgress();
                break;
            }
        }
        SetState(State::Stopped);
    }

    std::mutex m_mutex;
    std::condition_variable m_cv;
    State m_state{ State::Initial };
    std::thread m_thread;
};

// Meyer's singleton
// Allows console handler to access the BusinessLogic instance
BusinessLogic& GetBusinessLogic()
{
    static BusinessLogic businessLogic;
    return businessLogic;
}

BOOL WINAPI SetConsoleHandler(DWORD signal)
{
    if ((signal == CTRL_C_EVENT) || (signal == CTRL_BREAK_EVENT) || (signal == CTRL_CLOSE_EVENT))
    {
        std::cout << "Control flow abort received\n";
        GetBusinessLogic().Stop();
        std::this_thread::sleep_for(std::chrono::seconds(3)); // Allow user to see the output before finally shutting down
    }

    return TRUE;
}

int main()
{
    auto& businessLogic = GetBusinessLogic();
    auto retval = ::SetConsoleCtrlHandler(SetConsoleHandler, TRUE);

    businessLogic.Run();

    // Allow businessLogic to run for 10 seconds
    std::this_thread::sleep_for(std::chrono::seconds(10));

    businessLogic.Stop();
    return 0;
}

Я думаю, что этот код слишком длинный и его можно сократить. Можете ли вы сократить этот код? Возможно, в 20 строк. (PS. Когда я запустил ваш код, возникла ошибка, не знаю почему)

RandomGuy 29.07.2024 08:35

Нет, это не слишком долго, почему вы так думаете? При работе с потоками и синхронизацией необходимо проделать работу, чтобы завершение работы велось красиво и правильно (здесь более 30 лет опыта в этом отношении).

Pepijn Kramer 29.07.2024 09:21

И что за ошибка вылезла?

Pepijn Kramer 29.07.2024 09:21

Понял. Спасибо за объяснение. (Ошибка: после вывода «..........Сохранение прогресса» при запуске ~BusinessLogic() появилась ошибка с сообщением «запрошен фатальный выход из программы».)

RandomGuy 29.07.2024 09:33

Я заметил разницу при запуске отладчика (он добавляет свой дополнительный обработчик).

Pepijn Kramer 29.07.2024 09:43

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

Невозможно зарегистрировать несколько выходных данных с помощью WriteConsoleA в программе сборки Windows 10 (64-разрядной версии)
Не удалось скомпилировать точный листинг сборки, созданный компилятором
Окно не получает фокус даже при перемещении вверх
После перемещения файла в другое место и последующего создания файла с тем же именем в исходном месте время создания неверно
Java Graphics2D.drawImage() отображает размытое изображение только в Windows с масштабированием 125% и только в версиях Java после Java 8
Кто-нибудь знает, почему моя переменная (x) изменила свое значение с 3 на 0 (1 после x++) в этой программе? Я пытаюсь создать систему регистрации, но она перезаписывает
Как я могу установить текстовое значение в соответствии с позицией
Можно ли определить функцию C++, в которой один параметр передается через регистр EAX?
Получение имени пользователя Windows в формате «Имя Фамилия», когда пользователь является частью группы
Мой чат-сервер Python не отправляет сообщения должным образом