Мы не можем работать в среде, в которой мы можем гарантировать, что ни одна из наших задач не будет выполнена. В этом случае вышеприведенные реализации<wait_first_something()
>были бы naïve: как упоминалось вразделе «Управление волокнами», непойманное исключение в одном из наших волокон задачи вызвало бы вызов<std::terminate()
>.
Давайте хотя бы позаботимся о том, чтобы такое исключение распространилось на волокно, ожидающее первого результата. Мы можем использовать<future<>
>для перевозки либо возвратного значения, либо исключения. Мы изменим.<wait_first_value()
><unbounded_channel<>
>держать<future<
T>
>предметов вместо просто<T
>.
Как только у нас есть<future<>
>в руке, все, что нам нужно сделать, это позвонить<future::get()
>, который либо вернет значение, либо отбросит исключение.
template< typename Fn, typename ... Fns >
typename std::result_of< Fn() >::type
wait_first_outcome( Fn && function, Fns && ... functions) {
typedef typename std::result_of< Fn() >::type return_t;
typedef boost::fibers::future< return_t > future_t;
typedef boost::fibers::unbounded_channel< future_t > channel_t;
auto channelp(std::make_shared< channel_t >() );
wait_first_outcome_impl< return_t >( channelp,
std::forward< Fn >( function),
std::forward< Fns >( functions) ... );
future_t future( channelp->value_pop() );
channelp->close();
return future.get();
}
До сих пор так хорошо и #8212, но есть проблема с временем. Как нам получить<future<>
>до<unbounded_channel::push()
>на канале?
Мы могли бы позвонить<fibers::async()
>. Это, безусловно, приведет к<future<>
>задаче. Проблема в том, что он вернется слишком быстро! Мы хотим, чтобы<future<>
>пунктовзавершилизадачи на нашем<unbounded_channel<>
>. Мы желаем только того, кто будет первым.<future<>
>Если бы каждое волокно, запущенное<wait_first_outcome()
>, было<push()
>результатом вызова<async()
>, канал только сообщал бы результат самого левого пункта задачи —нетот, который завершается наиболее быстро.
Призыв<future::get()
>на будущее, возвращенное<async()
>, был бы неправильным. Вы можете позвонить<get()
>только один раз<future<>
>. И если бы было исключение, оно было бы переброшено внутрь волокна-помощника на конце канала производителя, а не размножено на конце потребителя.
Мы могли бы позвонить<future::wait()
>. Это будет блокировать волокна помощника, пока<future<>
>не станет готовым, и в этот момент мы можем<push()
>его получить<wait_first_outcome()
>.
Это сработает #8212, но есть более простая тактика, которая позволяет избежать создания дополнительного волокна. Мы можем обернуть функцию задачи в<packaged_task<>
>. В то время как кто-то, естественно, думает о том, чтобы передать<packaged_task<>
>новому волокну — то есть, на самом деле, то, что делает<async()
>— в этом случае мы уже запускаем вспомогательное волокно в конце канала производителя! Мы можем простоназвать<packaged_task<>
>. По возвращении с этого вызова функция задачи завершена, что означает, что<future<>
>, полученный от<packaged_task<>
>, наверняка будет готов. В этот момент мы можем просто<push()
>перейти на канал.
template< typename T, typename CHANNELP, typename Fn >
void wait_first_outcome_impl( CHANNELP channel, Fn && function) {
boost::fibers::fiber(
std::bind(
[]( CHANNELP & channel,
typename std::decay< Fn >::type & function) {
boost::fibers::packaged_task< T() > task( function);
task();
channel->push( task.get_future() );
},
channel,
std::forward< Fn >( function)
)).detach();
}
Называть это может выглядеть так:
std::string result = wait_first_outcome(
[](){ return sleeper("wfos_first", 50); },
[](){ return sleeper("wfos_second", 100); },
[](){ return sleeper("wfos_third", 150); });
std::cout << "wait_first_outcome(success) => " << result << std::endl;
assert(result == "wfos_first");
std::string thrown;
try {
result = wait_first_outcome(
[](){ return sleeper("wfof_first", 50, true); },
[](){ return sleeper("wfof_second", 100); },
[](){ return sleeper("wfof_third", 150); });
} catch ( std::exception const& e) {
thrown = e.what();
}
std::cout << "wait_first_outcome(fail) threw '" << thrown
<< "'" << std::endl;
assert(thrown == "wfof_first");