У меня есть программа, запущенная на консоли, и мне нужно сохранить прогресс, когда пользователь закрывает консоль. Как это сделать?
Пример:
void func()
{
while (true)
if (UserClosesTheConsole()) // Dunno how to detect
SaveProgess();
}
int main()
{
std::thread t(func);
}
Любое решение (не обязательно кроссплатформенное) приветствуется.
Да, я работаю на MSWindows и использую MSVC в качестве компилятора и Visual Studio в качестве среды.
Я бы сделал что-то вроде этого, поместив всю бизнес-логику внутри класса. Этот класс также отвечает за управление временем жизни основного цикла (потока). Я также использую условные переменные, чтобы обеспечить возможность оперативного завершения основного цикла.
#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. Когда я запустил ваш код, возникла ошибка, не знаю почему)
Нет, это не слишком долго, почему вы так думаете? При работе с потоками и синхронизацией необходимо проделать работу, чтобы завершение работы велось красиво и правильно (здесь более 30 лет опыта в этом отношении).
И что за ошибка вылезла?
Понял. Спасибо за объяснение. (Ошибка: после вывода «..........Сохранение прогресса» при запуске ~BusinessLogic() появилась ошибка с сообщением «запрошен фатальный выход из программы».)
Я заметил разницу при запуске отладчика (он добавляет свой дополнительный обработчик).
Вы работаете на MSWindows?