[英]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 需要訪問局部變量_ret
和cond
這些在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.