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

时间:2011-11-07 18:33:11

标签: c++ boost-asio shared-ptr boost-thread std-function

我已经在boost :: asio :: io_service周围创建了一个包装器来处理OpenGL应用程序的GUI线程上的异步任务。

任务可能是从其他线程创建的,因此boost::asio似乎是出于此目的的理想选择,这意味着我不需要编写自己的任务队列以及相关的互斥锁和锁定。我希望在每个帧上完成的工作低于可接受的阈值(例如5ms),因此我要调用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()

我想扩展此类的功能以允许取消任务。我之前的实现(使用几乎相同的接口)为每个任务返回了一个数字ID,并允许使用此ID取消任务。但是现在队列和相关锁定的管理由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管理是否安全?

1 个答案:

答案 0 :(得分:2)

是的,这是安全的。

代码:

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

只是做:

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

(和其他地方)。

请记住,您需要处理竞争条件,即在重置shared_ptr之前,可能会在weak_ptr上获取锁定,从而保持回调处于活动状态,因此即使您去了,也会偶尔看到回调在代码路径中重置回调shared_ptr。