а также подпрограммы с показом текущео прогресса в %?
Есть некая подпрограмма, которая делает что-то полезное и долго.
Как ее безопасно отменить выполнение этой подпрограммы?
псевдо-код:
void process_school(School& school)
{
for(class: school.classes())
{
process_class(class);
}
}
void process_class(Class& class_)
{
for(student: class_)
{
process_student(student);
}
}
void process_stundent(Student& student)
{
// ...
}
Скажем, в школе 1000 классов, и в кажом до 50 студентов. Время на обрабоку каждого студента — 10 сек.
Вообщем, функция (или метод) process_school() будет работать долго.
Клиент запускает эту подпрограмму в отдельном потоке.
Как безопасно реализовать прерываемость это подпрограммы?
Какие существуют общие подходы для этого?
Пока это реализовано при помощи класса хелпера, при помощи коллбэка (превдокод, вопрос в комментариях):
struct ICallback
{
virtual bool ReportProgress(int percent) = 0; // На вход принимает текущий прогресс в %, на выходе true - продолжать процесс, false - завершить
};
ICallback* g_callback;
class progress_helper
{
int m_expected_steps; // Ожидаемое число шагов в текущей подзадачи
int m_current_step; // Текущий шаг в текущей подзадачи
public:
int calculate_current_total_progress(); // Некий метод, который вычисляет и возвращает текущий прогресс (число от 0 до 100%)
progress_helper(int expected_steps) : m_expected_steps(expected_steps), m_current_step(0)
{
if (!g_callback.ReportProgress(calculate_current_total_progress()))
{
throw std::runtime_error("required job abort"); // Или использовать свой специальный тип JobAbort
}
}
void step()
{
++m_current_step;
update_current_progress(); // Некий метод, который обновляет текущий прогресс (число от 0 до 100%)
if (!g_callback.ReportProgress(calculate_current_total_progress()))
{
throw std::runtime_error("required job abort"); // Или использовать свой специальный тип JobAbort
}
}
};
void process_school(School& school)
{
progress_helper school_progress(school.get_class_count());
for(class: school.classes())
{
process_class(class);
school_progress.step();
}
}
void process_class(Class& class_)
{
progress_helper class_progress(school.get_student_count());
for(student: class_)
{
process_student(student);
class_progress.step();
}
}
void process_stundent(Student& student)
{
progress_helper student_progress(3);
process_name(student.name);
student_progress.step();
process_surname(student.surname);
student_progress.step();
process_sex(student.sex); // :)
student_progress.step();
}
// На самом деле создание классов progress_helper на стеке может
// каскадироваться, то есть, например, если в школе 10 классов,
// то progress_helper (в методе process_school) будет "тикать" 10 шагов,
// каждому шагу будет соответствовать 10% прогресса
// В функции process_class при создании progress_helper он уже будет
// "тикать" в диапозоне 10%, то есть первый раз от 0% до 10%, второй раз от 10% до 20% и так далее.
// Текущий диапозон прогресса дробится на число ожидаемых шагов, в хелперы вложенных функций уже работают
// с текущим диапозоном "програсса" и делят уже его.
// Но то, как вычисляется прогрессс - это не суть важно.
// Важно то, что при вызове конструктора или метода step() progress_helper при помощи коллбэка (тоже не важно какого- указатель на виртуальный интерфейс, или лямбда)
// если в коллбэке решили (то есть на вызывающей стороне) - что хватит, пора закруглятся и вернули false,
// то выбрасывается некое исключение, и текущий поток заверщается естественным обраов (с перехватом ожидаемого исключения JobAbort).
// Не нужно в таком случае "убивать" поток жекстко при помощи средств ОС.
// Минус этого подхода в том, что нужно расставлять локальные переменные типа progress_helper на стеке каждой функции.
// Какие уже есть опробированные решения для этого?