Я создал оболочку над boost :: asio :: io_service для обработки асинхронных задач в потоке GUI приложения OpenGL.
Задачи могут создаваться из других потоков, поэтому boost::asio
кажется идеальным для этой цели и означает, что мне не нужно писать собственную очередь задач со связанными мьютексами и блокировками. Я хочу, чтобы работа над каждым кадром была ниже приемлемого порога (например, 5 мс), поэтому я звоню poll_one
до тех пор, пока не будет превышен желаемый бюджет, а не звоню run
. Насколько я могу судить, это требует от меня звонить reset
всякий раз, когда публикуются новые задачи, что, кажется, работает хорошо.
Так как это коротко, вот и все, без #include
:
typedef std::function<void(void)> VoidFunc;
typedef std::shared_ptr<class UiTaskQueue> UiTaskQueueRef;
class UiTaskQueue {
public:
static UiTaskQueueRef create()
{
return UiTaskQueueRef( new UiTaskQueue() );
}
~UiTaskQueue() {}
// normally just hand off the results of std/boost::bind to this function:
void pushTask( VoidFunc f )
{
mService.post( f );
mService.reset();
}
// called from UI thread; defaults to ~5ms budget (but always does one call)
void update( const float &budgetSeconds = 0.005f )
{
// getElapsedSeconds is a utility function from the GUI lib I'm using
const float t = getElapsedSeconds();
while ( mService.poll_one() && getElapsedSeconds() - t < budgetSeconds );
}
private:
UiTaskQueue() {}
boost::asio::io_service mService;
};
Я сохраняю экземпляр UiTaskQueueRef в своем классе основного приложения и вызываю mUiTaskQueue->update()
из цикла анимации моего приложения.
Я бы хотел расширить функциональность этого класса, чтобы разрешить отмену задачи. Моя предыдущая реализация (с использованием почти того же интерфейса) возвращала числовой идентификатор для каждой задачи и позволяла отменять задачи с использованием этого идентификатора. Но теперь управление очередью и связанной блокировкой обрабатывается boost::asio
Я не уверен, как лучше это сделать.
Я попытался обернуть все задачи, которые я мог бы отменить в shared_ptr
, и создать объект-обертку, который сохраняет weak_ptr
для задачи и реализует оператор ()
, чтобы его можно было передать io_service
. Это выглядит так:
struct CancelableTask {
CancelableTask( std::weak_ptr<VoidFunc> f ): mFunc(f) {}
void operator()(void) const {
std::shared_ptr<VoidFunc> f = mFunc.lock();
if (f) {
(*f)();
}
}
std::weak_ptr<VoidFunc> mFunc;
};
У меня перегрузка моего pushTask
метода, который выглядит следующим образом:
void pushTask( std::weak_ptr<VoidFunc> f )
{
mService.post( CancelableTask(f) );
mService.reset();
}
Затем я отправляю отменяемые задачи в очередь, используя:
std::function<void(void)> *task = new std::function<void(void)>( boost::bind(&MyApp::doUiTask, this) );
mTask = std::shared_ptr< std::function<void(void)> >( task );
mUiTaskQueue->pushTask( std::weak_ptr< std::function<void(void)> >( mTask ) );
Или с VoidFunc
typedef, если вы предпочитаете:
VoidFunc *task = new VoidFunc( std::bind(&MyApp::doUiTask, this) );
mTask = std::shared_ptr<VoidFunc>( task );
mUiTaskQueue->pushTask( std::weak_ptr<VoidFunc>( mTask ) );
Пока я держу значения от shared_ptr
до mTask
, тогда io_service
будет выполнять задачу. Если я позвоню reset
на mTask
, weak_ptr
не сможет заблокироваться, и задание будет пропущено по желанию.
Мой вопрос - действительно вопрос доверия со всеми этими новыми инструментами: new std::function<void(void)>( std::bind( ... ) )
нормально ли это делать и безопасно ли управлять с помощью shared_ptr
?