Параметром шаблона класса std::packaged_task<>
является сигнатура функции, например void()
для функции, которая не принимает никаких параметров и не возвращает значения, или int(std::string&, double*)
для функции, которая принимает неконстантную ссылку на std::string
и указатель на double
и возвращает значение типа int
. При конструировании экземпляра std::packaged_task
вы обязаны передать функцию или допускающий вызов объект, который принимает параметры указанных типов и возвращает значение типа, преобразуемого в указанный тип возвращаемого значения. Точного совпадения типов не требуется; можно сконструировать объект std::packaged_task
из функции, которая принимает int
и возвращает float
, потому что между этими типами существуют неявные преобразования.
Тип возвращаемого значения, указанный в сигнатуре функции, определяет тип объекта std::future<>
, возвращаемого функцией-членом get_future()
, а заданный в сигнатуре список аргументов используется для определения сигнатуры оператора вызова в классе упакованной задачи. Например, в листинге ниже приведена часть определения класса std::packaged_task*, int)>
.
Листинг 4.8.Определение частичной специализации std::packaged_task
template<>
class packaged_task*, int)> {
public:
template
explicit packaged_task(Callable&& f);
std::future get_future();
void operator()(std::vector*, int);
};
Таким образом, std::packaged_task
— допускающий вызов объект, и, значит, его можно обернуть объектом std::function
, передать std::thread
в качестве функции потока, передать любой другой функции, которая ожидает допускающий вызов объект, или даже вызвать напрямую. Если std::packaged_task
вызывается как объект-функция, то аргументы, переданные оператору вызова, без изменения передаются обернутой им функции, а возвращенное значение сохраняется в виде асинхронного результата в объекте std::future
, полученном от get_future()
. Следовательно, мы можем обернуть задачу в std::packaged_task
и извлечь будущий результат перед тем, как передавать объект std::packaged_task
в то место, из которого он будет в свое время вызван. Когда результат понадобится, нужно будет подождать готовности будущего результата. В следующем примере показано, как всё это делается на практике.
Передача задач между потоками
Во многих каркасах для разработки пользовательского интерфейса требуется, чтобы интерфейс обновлялся только в специально выделенных потоках. Если какому-то другому потоку потребуется обновить интерфейс, то он должен послать сообщение одному из таких выделенных потоков, чтобы тот выполнил операцию. Шаблон std::packaged_task
позволяет решить эту задачу, не заводя специальных сообщений для каждой относящейся к пользовательскому интерфейсу операции.
Листинг 4.9.Выполнение кода в потоке пользовательского интерфейса с применением std::packaged_task
#include
#include
#include
#include
#include
std::mutex m;
std::deque> tasks;
bool gui_shutdown_message_received();
void get_and_process_gui_message();
void gui_thread() { ←
(1)
while (!gui_shutdown_message_received()) { ←
(2)
get_and_process_gui_message(); ←
(3)
std::packaged_task task; {
std::lock_guard lk(m);
if (tasks empty()) ←
(4)
continue;
task = std::move(tasks.front()); ←
(5)
tasks.pop_front();
}
task(); ←
(6)
}
}
std::thread gui_bg_thread(gui_thread);
template
std::future post_task_for_gui_thread(Func f) {
std::packaged_task task(f); ←
(7)
std::future res = task.get_future();←
(8)
std::lock_guard lk(m);
tasks.push_back(std::move(task)); ←
(9)
return res; ←
(10)
}
Код очень простой: поток пользовательского интерфейса (1)повторяет цикл, пока не будет получено сообщение о необходимости завершить работу (2). На каждой итерации проверяется, есть ли готовые для обработки сообщения GUI (3), например события мыши, или новые задачи в очереди. Если задач нет (4), программа переходит на начало цикла; в противном случае извлекает задачу из очереди (5), освобождает защищающий очередь мьютекс и исполняет задачу (6). По завершении задачи будет готов ассоциированный с ней будущий результат.
Помещение задачи в очередь ничуть не сложнее: по предоставленной функции создается новая упакованная задача (7), для получения ее будущего результата вызывается функция-член get_future()
(8), после чего задача помещается в очередь (9)еще до того, как станет доступен будущий результат (10). Затем часть программы, которая отправляла сообщение потоку пользовательского интерфейса, может дождаться будущего результата, если хочет знать, как завершилась задача, или отбросить его, если это несущественно.
Читать дальше