PPL when_all с задачами разных типов?

Я хотел бы использовать PPL "when_all" для задач разных типов. И добавьте вызов «тогда» к этой задаче.

Но when_all возвращает задачу, которая принимает вектор, поэтому все элементы должны быть одного типа. Итак, как мне это сделать?

Это то, что я придумал, но это похоже на взлом:

//3 different types:
int out1;
float out2;
char out3;

//Unfortunately I cant use tasks with return values as the types would be  different ...
auto t1 = concurrency::create_task([&out1](){out1 = 1; }); //some expensive operation
auto t2 = concurrency::create_task([&out2](){out2 = 2; }); //some expensive operation
auto t3 = concurrency::create_task([&out3](){out3 = 3; }); //some expensive operation

auto t4 = (t1 && t2 && t3); //when_all doesnt block

auto t5 = t4.then([&out1, &out2, &out3](){
    std::string ret = "out1: " + std::to_string(out1) + ", out2: " + std::to_string(out2) + ", out3: " + std::to_string(out3);
    return ret;
});
auto str = t5.get();

std::cout << str << std::endl;

У кого-нибудь есть идея получше?

(parallel_invoke блокирует, поэтому я не хочу его использовать)


person petke    schedule 20.06.2015    source источник


Ответы (1)


Целевые группы будут работать.

В противном случае:

template<class...Ts>
struct get_many{
  std::tuple<task<Ts>...> tasks;
  template<class T0, size_t...Is>
  std::tuple<T0,Ts...>
  operator()(
    std::task<T0> t0,
    std::index_sequence<Is...>
  ){
    return std::make_tuple(
      t0.get(),
      std::get<Is>(tasks).get()...
    );
  }
  template<class T0>
  std::tuple<T0,Ts...>
  operator()(task<T0> t0){
    return (*this)(
      std::move(t0),
      std::index_sequence_for<Ts...>{}
    );
  }
};

template<class T0, class...Ts>
task<std::tuple<T0,Ts...>> when_every(
  task<T0> next, task<Ts>... later
){
  return next.then( get_many<Ts...>{
    std::make_tuple(std::move(later)...)
  } );
}

который не работает с void задачами, но в остальном объединяет любой набор задач в задачу кортежей.

Заставить void работать немного сложнее. Один из способов — написать get_safe, который возвращает T вместо task<T> и void_placeholder вместо task<void>, а затем отфильтровать полученный кортеж перед возвратом. Или напишите partition_args, который разбивает аргументы на task<void> и task<T>, и действуйте по-разному с ними двумя. Оба вызывают головную боль. Мы также могли бы использовать шаблон добавления кортежа (где мы имеем дело с задачами по одной и можем либо добавить T к tuple, либо ничего не делать для void).

Он использует две функции библиотеки C++14 (index_sequence и index_sequence_for), но обе их легко написать на C++11 (по 2-4 строки каждая), и их реализации легко найти.

Я забыл, можно ли копировать задачу, я предположил, что это не было в приведенном выше коде. Если его можно скопировать, будет работать более короткая версия приведенного выше кода. Приносим извинения за любые опечатки.

person Yakk - Adam Nevraumont    schedule 20.06.2015
comment
Спасибо. кортеж вместо вектора делает приятный интерфейс. Я могу ошибаться здесь, но у меня сложилось впечатление, что мне следует избегать вызова get (или ожидания). Поскольку они блокируют и убивают масштабируемость. Поэтому реализация пользовательского when_all, который вызовы попадают под прикрытие, кажется мне неправильной. Глядя на реализацию concurrency::when_all, похоже, что get вызывается только после завершения всех задач, поэтому он не будет блокироваться. Но я не в своей тарелке. Наконец, я не уверен, как я могу связать и объединить продолжения задач вместе неблокирующим образом. - person petke; 21.06.2015
comment
@someone get вызывается только внутри then, а код then выполняется асинхронно (я предполагаю, что он возвращает task), надеюсь, в потоке первой задачи. - person Yakk - Adam Nevraumont; 21.06.2015
comment
Круто, мой плохой. Спасибо. - person petke; 21.06.2015