我使用的是来自std::async
的{{1}}和std::future
的组合。我正在使用我在代码中执行某项活动的time_out,当我尝试连接到服务器时可能需要时间。
以下是代码:
C++ 11
在大多数情况下,事情都很好。未来超时&amp;据报道,在很多情况下都准备好但是,我观察到的奇怪行为是,在某些情况下,UI会因为#include <future>
#include <chrono>
std::size_t PotentiallyLongRunningActivity() {
using namespace std::chrono_literals;
std::this_thread::sleep_for(10000s);
return 10;
}
bool DoActivity() {
bool activity_done = false;
auto my_future_result(std::async(std::launch::async, []() {
return PotentiallyLongRunningActivity(); //returns size_t
}));
std::future_status my_future_status = my_future_result.wait_for(std::chrono::milliseconds(800));
if (my_future_status == std::future_status::timeout) {
activity_done = false;
}
else if (my_future_status == std::future_status::ready) {
if (my_future_result.valid() && my_future_result.get() > 0) {
activity_done = true;
}
}
return activity_done;
//my_future_result hangs while exiting this method !!!
}
int main(int argc, char *argv[])
{
DoActivity();
return 0;
}
超出范围而挂起。我通过重复调用my_future_result
来确认这一点,如果在退出方法之前调用它,它永远不会返回。
我该如何解决这个问题?我有什么方法取消,删除或终止my_future_result.get()
?
答案 0 :(得分:6)
您在std::async
任务完成之前退出了您的功能。在某些情况下,std::future
的析构函数将阻塞,直到任务完成。
http://en.cppreference.com/w/cpp/thread/future/wait_for
此处在wait_for
的文档中,该示例显示超时后多次调用wait_for
,表明超时行为不会取消std::async
任务。
没有内置支持(我可以发现)允许线程被外部杀死。这是有道理的,因为如果以这种方式终止线程,则无法正确清理线程使用的系统资源的状态。
相反,最好将超时逻辑放在线程本身中,以便它可以自行终止并正确清理。
答案 1 :(得分:2)
作为一般规则,丢失线程的跟踪非常糟糕。当main
退出时,代码在另一个线程中运行是未定义行为的接收者。
因此,从std::future
返回的std::async
具有特殊属性,它会在销毁时等待std::async
完成。
这就是你所描述的“挂起”。这不是一个悬念 - 它正在等待任务完成。
C ++ 11中的线程原语是原语;它们不是完整功能应用程序的完成类型。您可以使用它们来编写线程池延迟任务队列等;天真地使用它们“在原始”往往会导致它们偏向于正确,但却没有给你你想要的东西。
一个简单的线程池就是:
template<class T>
struct threaded_queue {
using lock = std::unique_lock<std::mutex>;
void push_back( T t ) {
{
lock l(m);
data.push_back(std::move(t));
}
cv.notify_one();
}
boost::optional<T> pop_front() {
lock l(m);
cv.wait(l, [this]{ return abort || !data.empty(); } );
if (abort) return {};
auto r = std::move(data.back());
data.pop_back();
return std::move(r);
}
void terminate() {
{
lock l(m);
abort = true;
data.clear();
}
cv.notify_all();
}
~threaded_queue()
{
terminate();
}
private:
std::mutex m;
std::deque<T> data;
std::condition_variable cv;
bool abort = false;
};
struct thread_pool {
thread_pool( std::size_t n = 1 ) { start_thread(n); }
thread_pool( thread_pool&& ) = delete;
thread_pool& operator=( thread_pool&& ) = delete;
~thread_pool() = default; // or `{ terminate(); }` if you want to abandon some tasks
template<class F, class R=std::result_of_t<F&()>>
std::future<R> queue_task( F task ) {
std::packaged_task<R()> p(std::move(task));
auto r = p.get_future();
tasks.push_back( std::move(p) );
return r;
}
template<class F, class R=std::result_of_t<F&()>>
std::future<R> run_task( F task ) {
if (threads_active() >= total_threads()) {
start_thread();
}
return queue_task( std::move(task) );
}
void terminate() {
tasks.terminate();
}
std::size_t threads_active() const {
return active;
}
std::size_t total_threads() const {
return threads.size();
}
void clear_threads() {
terminate();
threads.clear();
}
void start_thread( std::size_t n = 1 ) {
while(n-->0) {
threads.push_back(
std::async( std::launch::async,
[this]{
while(auto task = tasks.pop_front()) {
++active;
try{
(*task)();
} catch(...) {
--active;
throw;
}
--active;
}
}
)
);
}
}
private:
std::vector<std::future<void>> threads;
threaded_queue<std::packaged_task<void()>> tasks;
std::atomic<std::size_t> active;
};
现在你在某处创建一些线程池,在其上抛出任务,你可以等待有问题的未来。池中有一定数量的线程。
run_task
将确保有一个线程可以运行您排队的任何任务。如果可用,queue_task
将仅使用现有线程。
返回的std::future<void>
不会阻止任务完成;但thread_pool
对象的析构函数确实如此。
请注意,它将中止所有排队的任务,并等待当前正在运行的任务完成,默认情况下是在销毁时。
包裹unique_ptr<thread_pool>
的东西对于容易移动很有用。必须禁用移动,因为活动线程保持指针指向 - this
。
thread_pool
具有讽刺意味的线程安全性;这是因为我们不守护std::vector<std::future<void>> threads;
;我的意思是,除了线程本身存储的线程安全。它被设计为只能由一个外部线程直接访问。
queue_task
和terminate
主要是线程安全的。
答案 2 :(得分:1)
从cppreference sample开始,只有&#34;开始&#34;,&#34; f2完成&#34;和#34;结束&#34;将从此代码打印(因为f1
没有&#34;挂起&#34;):
#include <future>
#include <thread>
#include <iostream>
int main() {
using namespace std::literals;
{
std::packaged_task<int()> task([]() {
std::this_thread::sleep_for(5s);
std::cout << "f1 finished" << std::endl;
return 42;
});
std::future<int> f1 = task.get_future();
std::thread(std::move(task)).detach();
std::future<int> f2 = std::async(std::launch::async, []() {
std::this_thread::sleep_for(3s);
std::cout << "f2 finished" << std::endl;
return 42;
});
f1.wait_for(1s);
f2.wait_for(1s);
std::cout << "the start" << std::endl;
}
// std::this_thread::sleep_for(7s);
std::cout << "the end" << std::endl;
}
如需进行良好讨论,请参阅:http://scottmeyers.blogspot.com.br/2013/03/stdfutures-from-stdasync-arent-special.html。
C ++标准库不支持线程终止操作。
注意你detach
的主题。分离本身并不是非常糟糕的,例如,它可能在用户可终止的守护进程中很有用,或者如果您对编排和拆卸有其他想法。否则,标准库将无法提供detach
。
答案 3 :(得分:1)
错误的原因是因为没有告诉编译器函数DoModify()的结果是异步可用的(因此被声明为std :: future&lt;&gt;)并且它期望bool的同步结果那种没有到达的类型。您可以使用std :: future :: is_ready()或std :: future_status。这是一个示例代码段
std::future<size_t> DoActivity()
{
return std::async(std::launch::async, []()
{
return PotentiallyLongRunningActivity();
});
}
int main()
{
auto result = DoActivity();
if ( result. Is_ready() )
{
auto data = result.get();
//do something with data
}
}