将std::future存储为成员变量并反复覆盖是否安全?附代码示例
问题
我尝试用std::async在单独线程中执行启停外部设备的耗时任务,确保GUI在操作过程中保持响应。我知道std::async返回的std::future在离开作用域时会等待异步任务完成,所以把这个future存在成员变量里。但如果从不等待这个future,还反复覆盖成员变量会发生什么?下面的代码是否安全?
class StartStop { public: enum State {STARTING, STARTED, STOPPING, STOPPED}; StartStop() { m_legalStateTransitions[(int)State::STARTING] = (int)State::STARTED; m_legalStateTransitions[(int)State::STARTED] = (int)State::STOPPING; m_legalStateTransitions[(int)State::STOPPING] = (int)State::STOPPED; m_legalStateTransitions[(int)State::STOPPED] = (int)State::STARTING; } bool startInSeparateThread() { if (!changeState(State::STARTING)) { return false; } m_startFuture = std::async(std::launch::async, [this]() { // do the starting, takes several seconds changeState(State::STARTED); }); return true; } bool stopInSeparateThread() { if (!changeState(State::STOPPING)) { return false; } m_stopFuture = std::async(std::launch::async, [this]() { // do the stopping, takes several seconds changeState(State::STOPPED); }); return true; } private: bool changeState(State state) { std::lock_guard<std::mutex> guard(m_stateMutex); if (m_legalStateTransitions[(int)m_state] == state) { m_state = state; return true; } return false; } std::array<int, 4> m_legalStateTransitions; State m_state = State::STOPPED; std::mutex m_stateMutex; std::future<void> m_startFuture; std::future<void> m_stopFuture; };
startInSeparateThread()和stopInSeparateThread()会在用户按下启停按钮时被GUI线程反复调用。
分析与解答
一、反复覆盖std::future成员变量的行为
当你给一个std::future对象赋值新的std::future实例时,旧的std::future会被析构。对于由std::async(std::launch::async)创建的future,其析构函数会阻塞当前线程,直到对应的异步任务执行完毕。这意味着:
- 如果在旧的异步任务(比如启动操作)还未完成时强行覆盖对应的
m_startFuture,执行赋值的GUI线程会被卡住,直到旧任务结束——这直接破坏了你用异步任务保持GUI响应的初衷。
二、当前代码的安全性分析
从当前代码的状态机逻辑来看,正常情况下不会出现反复覆盖future的情况,原因如下:
startInSeparateThread()只有在当前状态为STOPPED时,才能成功调用changeState(STARTING),进而执行future赋值操作。一旦进入STARTING状态,后续再调用该函数会因状态转换不合法直接返回false,不会触发新的future赋值。- 同理,
stopInSeparateThread()仅在STARTED状态下可执行,进入STOPPING状态后,后续调用会直接返回false。
但代码仍存在几个潜在风险:
- 未处理异步任务异常:如果启动/停止任务的代码抛出异常,
std::future会存储该异常,但当前代码从未调用m_startFuture.get()或wait(),异常会在future析构时被忽略,可能导致设备状态与程序内部状态不一致。 - 状态变更无通知机制:异步任务中修改状态后,GUI无法及时感知(比如无法更新按钮的可点击状态),可能导致用户操作混乱。
- 隐性阻塞的潜在风险:如果未来修改状态机逻辑,允许在异步任务未完成时覆盖future,GUI线程会被意外阻塞,彻底丧失响应性。
三、优化建议
- 主动管理任务生命周期:若需支持取消未完成的异步任务,不要依赖
std::future的析构阻塞,应设计可中断的任务逻辑(比如用原子标志位让任务主动退出)。 - 处理异步任务异常:在合适的时机调用
future.get(),捕获并处理任务中抛出的异常,避免状态不一致。 - 添加状态变更通知:借助回调函数、信号槽(如Qt框架)让GUI及时感知状态变化,同步更新界面元素。
- 规避隐性阻塞:如果必须允许覆盖未完成的future,不要直接赋值,可先主动等待旧任务结束,或用
std::shared_future等方式管理,确保GUI线程不被意外阻塞。
内容的提问来源于stack exchange,提问作者Andy
相关产品推荐
相关产品推荐

