РЕДАКТИРОВАТЬ: Вы упоминаете, что не хотите «разбивать функцию на части до / после ожидания».
На каком языке вы развиваете? Если он имеет продолжения (yield return
в C #), то это обеспечивает способ написания кода, который представляется процедурным, но который можно легко приостановить до тех пор, пока блокирующая операция не выполнит обратный вызов завершения.
Вот статья об идее: http://msdn.microsoft.com/en-us/magazine/cc546608.aspx
UPDATE:
К сожалению, язык C ++
Это сделало бы отличный слоган футболки.
Хорошо, так что вам может быть полезно структурировать ваш последовательный код как конечный автомат, чтобы он мог работать с прерываниями / возобновлением.
например. вашей боли нужно написать две функции, одну из которых инициирует, а другую выполняет роль обработчика события завершения:
void send_greeting(const std::string &msg)
{
std::cout << "Sending the greeting" << std::endl;
begin_sending_string_somehow(msg, greeting_sent_okay);
}
void greeting_sent_okay()
{
std::cout << "Greeting has been sent successfully." << std::endl;
}
Ваша идея была ждать:
void send_greeting(const std::string &msg)
{
std::cout << "Sending the greeting" << std::endl;
waiter w;
begin_sending_string_somehow(msg, w);
w.wait_for_completion();
std::cout << "Greeting has been sent successfully." << std::endl;
}
В этом примере waiter
перегружает operator (), чтобы он мог выполнять функцию обратного вызова, и wait_for_completion
как-то зависает до тех пор, пока не увидит, что operator () был вызван.
Я предполагаю, что второй параметр begin_sending_string_somehow
является параметром шаблона, который может быть любым вызываемым типом, не принимающим параметры.
Но, как вы говорите, у этого есть недостатки. Каждый раз, когда поток ожидает так, вы добавляете еще одну потенциальную тупиковую ситуацию, и вы также потребляете «ресурс» всего потока и его стека, а это означает, что потребуется создать больше потоков в другом месте, чтобы можно было выполнить работу , что противоречит всему смыслу пула потоков.
Вместо этого напишите класс:
class send_greeting
{
int state_;
std::string msg_;
public:
send_greeting(const std::string &msg)
: state_(0), msg_(msg) {}
void operator()
{
switch (state_++)
{
case 0:
std::cout << "Sending the greeting" << std::endl;
begin_sending_string_somehow(msg, *this);
break;
case 1:
std::cout << "Greeting has been sent successfully."
<< std::endl;
break;
}
}
};
Класс реализует оператор вызова функции ()
. Каждый раз, когда он вызывается, он выполняет следующий шаг в логике. (Конечно, будучи таким тривиальным примером, теперь это в основном шум управления состоянием, но в более сложном примере с четырьмя или пятью состояниями это может помочь прояснить последовательную природу кода).
Проблемы:
Если сигнатура функции обратного вызова события имеет специальные параметры, вам нужно добавить еще одну перегрузку operator()
, которая сохраняет параметры в дополнительных полях и затем вызывает перегрузку без параметров. Затем он начинает запутываться, потому что эти поля будут доступны во время компиляции в начальном состоянии, даже если они не имеют смысла во время выполнения в этом состоянии.
Как объекты класса создаются и удаляются? Объект должен выжить, пока операция не завершится или не будет прекращена ... центральная ловушка C ++. Я бы порекомендовал реализовать общую схему управления этим. Создайте список «вещей, которые нужно будет удалить» и убедитесь, что это происходит автоматически в определенных безопасных точках, то есть постарайтесь максимально приблизиться к GC, насколько это возможно. Чем дальше вы находитесь от этого, тем больше памяти вы утечете.