繁体   English   中英

C++ function 超时包装器

[英]C++ function timeout wrapper

基于How to implement timeout for function in c++ ,我写了这个包装器:

template <typename t_time, typename t_function, typename... t_params>
inline std::conditional_t<
    // if 't_function' return type is 'void'
    std::is_void_v<std::invoke_result_t<t_function, const bool &, t_params...>>,
    // the 'execute' wrapper will return 'bool', which will be 'true' if the
    // 'p_function' executes in less 'p_max_time', or 'false' otherwise
    bool,
    // else it will result a 'std::optional' with the return type of
    // 't_function', which will contain a value of that type, if the
    // 'p_function' executes in less 'p_max_time', or empty otherwise
    std::optional<std::invoke_result_t<t_function, const bool &, t_params...>>>
execute(t_time p_max_time, t_function &p_function, t_params &&... p_params) {
  std::mutex _mutex;
  std::condition_variable _cond;
  bool _timeout{false};

  typedef typename std::invoke_result_t<t_function, const bool &, t_params...>
      t_ret;

  if constexpr (std::is_void_v<t_ret>) {
    std::thread _th([&]() -> void {
      p_function(_timeout, std::forward<t_params>(p_params)...);
      _cond.notify_one();
    });

    std::unique_lock<std::mutex> _lock{_mutex};
    if (_cond.wait_for(_lock, p_max_time) != std::cv_status::timeout) {
      _th.join();
      return true;
    }
    
    _timeout = true;
    _th.detach();
    return false;
  } else {
    t_ret _ret;

    std::thread _th([&]() -> void {
      _ret = p_function(_timeout, std::forward<t_params>(p_params)...);
      _cond.notify_one();
    });

    std::unique_lock<std::mutex> _lock{_mutex};
    if (_cond.wait_for(_lock, p_max_time) != std::cv_status::timeout) {
      _th.join();
      return {std::move(_ret)};
    }
    _timeout = true;
    
    _th.detach();
    return {};
  }
}

与我引用的问题答案中的代码不同,我不想在execute包装器中抛出异常。 如果p_function没有返回,包装器将返回一个bool ,如果p_function执行p_max_time则返回true ,否则返回false 如果p_function返回T ,则包装器将返回std::optional<T>如果p_function不超过p_max_time则它将具有一个值,否则它将为空。

p_function所需的const bool &参数用于通知p_function它的执行超过p_max_time ,因此p_function可能会停止执行,尽管execute不会依赖它。

这是一个例子:

    auto _function = [](const bool &p_is_timeout, int &&p_i) -> void {
      std::this_thread::sleep_for(1s);
      if (p_is_timeout) {
        std::cout << "timeout";
      } else {
        std::cout << "i = " << p_i << '\n';
      }
    };
    int _i{4};

    if (!async::execute(200ms, _function, std::move(_i))) {
      std::cout << "TIMEOUT!!\n";
    }

所以,问题是_th.detach()在我连续执行一些测试函数时导致崩溃。 我如果将其更改为_th.join() ,则不再发生崩溃,但显然,调用包装器的 function 必须等待p_function结束,这是不需要的。

如何在不导致崩溃的情况下execute分离_th

您的 lambda 需要访问局部变量_retcond这些在execute结束后不存在,因此您的代码具有未定义的行为。 仅当 lambda 与定义它们的代码具有相同的生命周期时,才应使用通过引用捕获的 Lambda。

您需要在堆中定义 state 变量,以便它们在 function 结束后存在。例如,您可以使用shared_ptr

template <typename Result>
struct state
{
  std::mutex _mutex;
  std::condition_variable _cond;
  Result _ret;
};
template <>
struct state<void>
{
  std::mutex _mutex;
  std::condition_variable _cond;
};

template <typename t_time, typename t_function, typename... t_params>
inline std::conditional_t<
    // if 't_function' return type is 'void'
    std::is_void_v<std::invoke_result_t<t_function, const bool &, t_params...>>,
    // the 'execute' wrapper will return 'bool', which will be 'true' if the
    // 'p_function' executes in less 'p_max_time', or 'false' otherwise
    bool,
    // else it will result a 'std::optional' with the return type of
    // 't_function', which will contain a value of that type, if the
    // 'p_function' executes in less 'p_max_time', or empty otherwise
    std::optional<std::invoke_result_t<t_function, const bool &, t_params...>>>
execute(t_time p_max_time, t_function &p_function, t_params &&... p_params) {
  typedef typename std::invoke_result_t<t_function, const bool &, t_params...>
      t_ret;
  auto _state = std::make_shared<state<t_ret>>();
  bool _timeout{false};

  if constexpr (std::is_void_v<t_ret>) {
    std::thread _th([&, _state]() -> void {
      p_function(_timeout, std::forward<t_params>(p_params)...);
      _state->_cond.notify_one();
    });

    std::unique_lock<std::mutex> _lock{_state->_mutex};
    if (_state->_cond.wait_for(_lock, p_max_time) != std::cv_status::timeout) {
      _th.join();
      return true;
    }
    
    _timeout = true;
    _th.detach();
    return false;
  } else {
    std::thread _th([&, _state]() -> void {
      _state->_ret = p_function(_timeout, std::forward<t_params>(p_params)...);
      _state->_cond.notify_one();
    });

    std::unique_lock<std::mutex> _lock{_state->_mutex};
    if (_state->_cond.wait_for(_lock, p_max_time) != std::cv_status::timeout) {
      _th.join();
      return {std::move(_state->_ret)};
    }
    _timeout = true;
    
    _th.detach();
    return {};
  }
}

请注意,为简单起见,我通过引用在 function arguments 和 function 的捕获中留下了注释,但您仍然需要确保这些引用在需要时保持有效(例如,如果超时时间很短,则 function 可以在目标之前退出function 执行,或者如果 arguments 是引用,则被调用的 function 不能在 function 退出后使用这些引用)。

如果你有 c++20,你可能想看看std::jthread

根据答案和建议,我想出了:

template <typename t_time, typename t_function, typename... t_params>
inline std::conditional_t<
    // if 't_function' return type is 'void'
    std::is_void_v<
        std::invoke_result_t<t_function, std::function<bool()>, t_params...>>,
    // the 'execute' wrapper will return 'bool', which will be 'true' if the
    // 'p_function' executes in less 'p_max_time', or 'false' otherwise
    bool,
    // else it will result a 'std::optional' with the return type of
    // 't_function', which will contain a value of that type, if the
    // 'p_function' executes in less 'p_max_time', or empty otherwise
    std::optional<
        std::invoke_result_t<t_function, std::function<bool()>, t_params...>>>
execute(t_time p_max_time, t_function &p_function, t_params &&... p_params) {
  std::mutex _mutex;
  std::condition_variable _cond;
  auto _timeout = std::make_shared<bool>(false);

  auto _is_timeout = [_timeout]() { return *_timeout; };

  typedef typename std::invoke_result_t<t_function, std::function<bool()>,
                                        t_params...>
      t_ret;

  if constexpr (std::is_void_v<t_ret>) {
    std::thread _th([&]() -> void {
      p_function(_is_timeout, std::forward<t_params>(p_params)...);
      _cond.notify_one();
    });

    std::unique_lock<std::mutex> _lock{_mutex};
    if (_cond.wait_for(_lock, p_max_time) != std::cv_status::timeout) {
      _th.join();
      return true;
    }
    *_timeout = true;
    _th.join();
    return false;
  } else {
    t_ret _ret;

    std::thread _th([&]() -> void {
      _ret = p_function(_is_timeout, std::forward<t_params>(p_params)...);
      _cond.notify_one();
    });

    std::unique_lock<std::mutex> _lock{_mutex};
    if (_cond.wait_for(_lock, p_max_time) != std::cv_status::timeout) {
      _th.join();
      return {std::move(_ret)};
    }
    *_timeout = true;
    _th.join();
    return {};
  }
}

这个例子变成了:

    auto _function = [](std::function<bool()> p_timeout, int &&p_i) -> void {
      std::this_thread::sleep_for(1s);
      if (p_timeout()) {
        std::cout << "timeout in work function\n";
      } else {
        std::cout << "i = " << p_i  << '\n';
      }
    };
    int _i{4};

    if (!execute(200ms, _function, std::move(_i))) {
       std::cout << "OK - timeout\n";
    }
    else {
       std::cout << "NOT OK - no timeout\n";
    }

我相信将std::function<bool()>传递给工作 function ( p_function ) 创建了一个关于execute将如何控制超时的良好抽象,以及p_function检查它的简单方法。

我还删除了std::thread::detach()调用。

暂无
暂无

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

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