Uses shared_ptr and weak_ptr to control std :: function safe lifetime?

I created a wrapper around boost :: asio :: io_service to handle asynchronous tasks in the OpenGL application GUI thread.

Tasks can be created from other threads, so boost::asio seems ideal for this purpose and means that I don’t need to write my own task queue using the associated mutexes and locking. I want the work to be performed on each frame below an acceptable threshold (for example, 5 ms), so I call poll_one until the desired budget is exceeded, instead of calling run . As far as I can explain this, I need to call reset whenever new tasks appear that seem to work well.

Since this is short, everything is here, without #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; }; 

I save an instance of UiTaskQueueRef in my main application class and call mUiTaskQueue->update() from the application animation loop.

I would like to extend the functionality of this class so that the task can be canceled. My previous implementation (using almost the same interface) returned a numeric identifier for each task and allowed the execution of tasks using this identifier. But now queue management and related locking are handled by boost::asio I'm not sure how best to do this.

I tried to wrap any tasks that I could cancel in shared_ptr and create a wrapper object that stores weak_ptr for the task and implements the operator () so that it can be passed to io_service . It looks like this:

 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; }; 

Then I have an overload of my pushTask method, which looks like this:

 void pushTask( std::weak_ptr<VoidFunc> f ) { mService.post( CancelableTask(f) ); mService.reset(); } 

Then I send the canceled tasks to the queue using:

 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 ) ); 

Or using VoidFunc typedef if you prefer:

 VoidFunc *task = new VoidFunc( std::bind(&MyApp::doUiTask, this) ); mTask = std::shared_ptr<VoidFunc>( task ); mUiTaskQueue->pushTask( std::weak_ptr<VoidFunc>( mTask ) ); 

As long as I save shared_ptr to mTask , then io_service will complete the task. If I call reset on mTask , then weak_ptr cannot block and the task is skipped as desired.

My question is really sure that all these new tools are: new std::function<void(void)>( std::bind( ... ) ) OK, what should I do and a safe thing to manage with shared_ptr ?

+4
source share
1 answer

Yes, it is safe.

For code:

 VoidFunc *task = new VoidFunc( std::bind(&MyApp::doUiTask, this) ); mTask = std::shared_ptr<VoidFunc>( task ); 

Just do:

 mTask.reset(new VoidFunc( std::bind(&MyApp::doUiTask, this) ) ); 

(and elsewhere).

Keep in mind that you need to deal with a race condition where protectors can get a lock on weak_ptr before you reset shared_ptr by supporting a callback, and as a result you will sometimes see callbacks, although you have omitted the code path by resetting the shared_ptr callback.

+2
source

Source: https://habr.com/ru/post/1380043/


All Articles