async && GCD

std::async из C++11 хорош практически всем: прост, удобен, универсален. И только одна особенность стандарта несколько портит этот праздник – нет четкой регламентации того, где и как должна выполниться асинхронная задача; задача может быть выполнена как в отдельном потоке, так и в пуле потоков. В итоге это приводит к тому, что разработчики STL не утруждают себя пулом потоков (даже при наличии оного в ОС по-умолчанию) и плодят по протоку на каждый std::async вызванный с флагом std::launch::async. В случае с macOS, как мне кажется, это довольно большая оплошность, так как в системе уже есть готовый пул потоков, которым остается только воспользоваться!
В итоге я немного поковырялся в стандарте, доступных реализациях и вышло у меня следующее:

template <class Function, class... Args>
std::future<std::result_of_t<std::decay_t<Function>( std::decay_t<Args>... )>>
async( std::launch policy, Function&& f, Args&&... args )
{
    using result_t   = typename std::result_of<Function( Args... )>::type;
    using packaged_t = typename std::packaged_task<result_t()>;

    auto task = new packaged_t( std::bind( std::forward<Function>( f ),       // (1)
                                           std::forward<Args>( args )... ) );
    auto res = task->get_future();
    dispatch_async_f(
        dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), task,
        []( void* task_ptr ) {                                                // (2)
            auto smart_task_ptr = std::unique_ptr<packaged_t>(                // (3)
                static_cast<packaged_t*>( task_ptr ) );

            ( *smart_task_ptr )();
        } );
    return res;
}

Довольно долго ломал голову на тему того, можно ли как-то обойтись без создания неуправляемого объекта типа packaged_t на шаге 1, но ничего путного не придумал. Если попробовать заменить голый указатель на std::unique_ptr и захватить его в лямбде на шаге 2, то тип лямбды перестанет быть void (*dispatch_function_t)(void *) и сборка сломается.
В итоге, для корректного удаления созданного объекта 1 в случае возникновения каких-либо непредвиденных ситуаций с вызовом функции Function, решил ограничиться использованием std::unique_ptr уже в самом асинхронном обработчике 3. Вроде ничего криминального не наблюдается и работает именно так, как мне и хотелось, но в целом не так красиво как могло бы быть.

Leave a Reply