}
if (connection->has_outgoing_data()) {←
(5)
outgoing_packet data =
connection->top_of_outgoing_queue();
connection->send(data.payload);
data.promise.set_value(true); ←
(6)
}
}
}
}
Функция process_connections()
повторяет цикл, пока done()
возвращает true
(1). На каждой итерации поочередно проверяется каждое соединение (2); если есть входящие данные, они читаются (3), а если в очереди имеются исходящие данные, они отсылаются (5). При этом предполагается, что в каждом входящем пакете хранится некоторый идентификатор и полезная нагрузка, содержащая собственно данные. Идентификатору сопоставляется объект std::promise
(возможно, путем поиска в ассоциативном контейнере) (4), значением которого является полезная нагрузка пакета. Исходящие пакеты просто извлекаются из очереди отправки и передаются но соединению. После завершения передачи в обещание, ассоциированное с исходящими данными, записывается значение true
, обозначающее успех (6). Насколько хорошо эта схема ложится на фактический сетевой протокол, зависит от самого протокола; в конкретном случае схема обещание/будущий результат может и не подойти, хотя структурно она аналогична поддержке асинхронного ввода/вывода в некоторых операционных системах.
В коде выше мы полностью проигнорировали возможные исключения. Хотя мир, в котором всё всегда работает правильно, был бы прекрасен, действительность не так радужна. Переполняются диски, не находятся искомые данные, отказывает сеть, «падает» база данных — всякое бывает. Если бы операция выполнялась в том потоке, которому нужен результат, программа могла бы просто сообщить об ошибке с помощью исключения. Но было бы неоправданным ограничением требовать, чтобы всё работало правильно только потому, что мы захотели воспользоваться классами std::packaged_task
или std::promise
.
Поэтому в стандартной библиотеке С++ имеется корректный способ учесть возникновение исключений в таком контексте и сохранить их как часть ассоциированного результата.
4.2.4. Сохранение исключения в будущем результате
Рассмотрим следующий коротенький фрагмент. Если передать функции square_root()
значение -1
, то она возбудит исключение, которое увидит вызывающая программа:
double square_root(double x) {
if (x<0) {
throw std::out_of_range("x<0");
}
return sqrt(x);
}
А теперь предположим, что вместо вызова square_root()
в текущем потоке
double y = square_root(-1);
мы вызываем ее асинхронно:
std::future f = std::async(square_root,-1);
double y = f.get();
В идеале хотелось бы получить точно такое же поведение: чтобы поток, вызывающий f.get()
, мог увидеть не только нормальное значение y
, но и исключение — как в однопоточной программе.
Что ж, именно так на самом деле и происходит: если функция, вызванная через std::async
, возбуждает исключение, то это исключение сохраняется в будущем результате вместо значения, а когда будущий результат оказывается готовым , вызов get()
повторно возбуждает сохраненное исключение. (Примечание: стандарт ничего не говорит о том, возбуждается ли исходное исключение или его копия; различные компиляторы и библиотеки вправе решать этот вопрос по-разному.) То же самое происходит, когда функция обернута объектом std::packaged_task
, — если при вызове задачи обернутая функция возбуждает исключение, то объект исключения сохраняется в будущем результате вместо значения, и это исключение повторно возбуждается при обращении к get()
.
Разумеется, std::promise
обеспечивает те же возможности в случае явного вызова функции. Чтобы сохранить исключение вместо значения, следует вызвать функцию-член set_exception()
, а не set_value()
. Обычно это делается в блоке catch
:
extern std::promise some_promise;
try {
some_promise.set_value(calculate_value());
} catch (...) {
some_promise.set_exception(std::current_exception());
}
Здесь мы воспользовались функцией std::current_exception()
, которая возвращает последнее возбужденное исключение, но могли вызвать std::copy_exception()
, чтобы поместить в объект-обещание новое исключение, которое никем не возбуждалось:
some_promise.set_exception(
std::copy_exception(std::logic_error("foo"));
Если тип исключения заранее известен, то это решение гораздо чище, чем использование блока try/catch
; мы не только упрощаем код, но и оставляем компилятору возможности для его оптимизации.
Читать дальше