#c #boost-asio #shared-ptr #boost-thread #std-function
#c #boost-asio #shared-ptr #boost-thread #std-функция
Вопрос:
Я создал оболочку вокруг 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 amp;budgetSeconds = 0.005f )
{
// getElapsedSeconds is a utility function from the GUI lib I'm using
const float t = getElapsedSeconds();
while ( mService.poll_one() amp;amp; 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(amp;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(amp;MyApp::doUiTask, this) );
mTask = std::shared_ptr<VoidFunc>( task );
mUiTaskQueue->pushTask( std::weak_ptr<VoidFunc>( mTask ) );
Пока я сохраняю shared_ptr
to mTask
, то io_service
будет выполняться задача. Если я вызываю reset
on mTask
, то weak_ptr
не удается заблокировать, и задача пропускается по желанию.
Мой вопрос действительно касается уверенности во всех этих новых инструментах: new std::function<void(void)>( std::bind( ... ) )
нормально ли это делать и безопасно ли управлять с помощью shared_ptr
?
Ответ №1:
Да, это безопасно.
Для кода:
VoidFunc *task = new VoidFunc( std::bind(amp;MyApp::doUiTask, this) );
mTask = std::shared_ptr<VoidFunc>( task );
Просто сделайте:
mTask.reset(new VoidFunc( std::bind(amp;MyApp::doUiTask, this) ) );
(и в других местах).
Имейте в виду, что вам нужно иметь дело с состоянием гонки, когда протектор может блокировать weak_ptr непосредственно перед сбросом shared_ptr, сохраняя обратный вызов, и в результате вы иногда будете видеть обратные вызовы, даже если вы пошли по пути кода, сбрасывая обратный вызов shared_ptr.
Комментарии:
1. Большое спасибо! Я думаю, что смогу избежать состояния гонки, только отменив из потока пользовательского интерфейса, где будут обрабатываться задачи, но я буду иметь это в виду для многопоточной версии этого кода.
2. Также спасибо за совет по инициализации!