[英]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.