简体   繁体   English

使用shared_ptr和weak_ptr来管理std :: function safe的生命周期?

[英]Is using shared_ptr and weak_ptr to manage lifetime of std::function safe?

I've created a wrapper around boost::asio::io_service to handle asynchronous tasks on the GUI thread of an OpenGL application. 我已经创建了一个包含boost :: asio :: io_service的包装器来处理OpenGL应用程序的GUI线程上的异步任务。

Tasks might be created from other threads so boost::asio seems ideal for this purpose and means I don't need to write my own task queue with associated mutexes and locking. 任务可能是从其他线程创建的,因此boost::asio似乎是这个目的的理想选择,这意味着我不需要编写自己的任务队列以及相关的互斥锁和锁定。 I want to keep the work done on each frame below an acceptable threshold (eg 5ms) so I'm calling poll_one until the desired budget is exceeded, rather than calling run . 我希望在每个帧上完成的工作低于可接受的阈值(例如5ms),所以我调用poll_one直到超出所需的预算,而不是调用run As far as I can tell this requires me to call reset whenever new tasks are posted, which seems to be working well. 据我所知,这要求我在发布新任务时调用reset ,这似乎运作良好。

Since it's short, here's the whole thing, sans #include : 因为它很短,所以这就是整个事情,没有#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 keep an instance of UiTaskQueueRef in my main app class and call mUiTaskQueue->update() from within my app's animation loop. 我在我的主应用程序类中保留了一个UiTaskQueueRef实例,并从我的app的动画循环中调用mUiTaskQueue->update()

I'd like to extend the functionality of this class to allow a task to be canceled. 我想扩展这个类的功能,以允许任务被取消。 My previous implementation (using almost the same interface) returned a numeric ID for each task and allowed tasks to be canceled using this ID. 我之前的实现(使用几乎相同的接口)为每个任务返回了一个数字ID,并允许使用此ID取消任务。 But now the management of the queue and associated locking is handled by boost::asio I'm not sure how best to do this. 但现在队列和相关锁定的管理由boost::asio处理我不知道如何最好地做到这一点。

I've made an attempt by wrapping any tasks I might want to cancel in a shared_ptr and making a wrapper object that stores a weak_ptr to the task and implements the () operator so it can be passed to the io_service . 我尝试将任何我想要取消的任务包装在shared_ptr并创建一个包装器对象,将weak_ptr存储到任务中并实现()运算符,以便将其传递给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;
};

I then have an overload of my pushTask method that looks like this: 然后我的pushTask方法的重载看起来像这样:

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

I then post cancelable 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 with the VoidFunc typedef if you prefer: 或者如果您愿意,可以使用VoidFunc typedef:

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

So long as I keep the shared_ptr to mTask around then the io_service will execute the task. 只要我将shared_ptr保持在mTask周围, io_service就会执行任务。 If I call reset on mTask then the weak_ptr can't lock and the task is skipped as desired. 如果我在mTask上调用reset ,则weak_ptr无法锁定,并且会根据需要跳过任务。

My question is really one of confidence with all these new tools: is new std::function<void(void)>( std::bind( ... ) ) an OK thing to be doing, and a safe thing to manage with a shared_ptr ? 我的问题实际上是对所有这些新工具充满信心: new std::function<void(void)>( std::bind( ... ) )是一件好事,可以安全地管理一个shared_ptr

Yes, this is safe. 是的,这是安全的。

For the 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). (和其他地方)。

Bear in mind that you need to deal with the race condition where a tread might be getting a lock on the weak_ptr just before you reset the shared_ptr keeping the callback alive, and as a result you will occasionally see callbacks even though you went down the code path resetting the callback shared_ptr. 请记住,您需要处理竞争条件,即在重置shared_ptr以保持回调活动之前,可能会在weak_ptr上获取锁定,因此即使您关闭了代码,您也会偶尔看到回调path重置回调shared_ptr。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM