簡體   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