std::future returned from std::async hangs while going out of scope

不羁岁月 提交于 2019-12-08 07:12:36

问题


I am using a combination of std::async and std::future from C++ 11. I am using to enforce a time_out on a certain activity that I do in my code which might take time as I try connecting to server.

Following is how the code is:

#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;
}

Things work fine in most of the cases. The future times out & is reported ready in many cases. But, Strange behavior I am observing is that in some cases the UI hangs because my_future_result when going out of scope hangs. I confirmed this by repeating the call to my_future_result.get() which never returns if called just before exiting the method.

How can I get around this ? Is there some way I cancel or delete or terminate the std::future ?


回答1:


Taking from cppreference sample, only "the start", "f2 finished" and "the end" will get printed from this code (because f1 doesn't "hang"):

#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;
}

For good discussion see: http://scottmeyers.blogspot.com.br/2013/03/stdfutures-from-stdasync-arent-special.html.

C++ standard library gives no support for thread kill operations.

Take care with threads you detach. Detachment per se is not "extremely bad", it may be useful in user terminable daemons for example, or if you have some other idea of orchestration and teardown. Otherwise, detach would have no point being provided by the standard library.




回答2:


You are exiting your function before the std::async task has completed. Under certain circumstances, the destructor for a std::future will block until the task has completed.

http://en.cppreference.com/w/cpp/thread/future/wait_for

Here in the documentation for wait_for the example shows multiple calls to wait_for after timeouts, showing that the act of timing out does not cancel the std::async task.

There is no built-in support (that I could discover) that allows for threads to be killed externally. This makes sense as there isn't a way to properly clean up the state of system resources in use by the thread if it is terminated in this way.

Instead, it is better to place the timeout logic in the thread itself, so that it can terminate itself and clean up properly.




回答3:


As a general rule, losing track of a thread is extremely bad. Having code running in another thread when main exits is a recipie of undefined behavior.

As such, the std::future that returns from std::async has the special property that it will wait for the std::async to complete when it is destroyed.

This is what you are describing as a "hang". It isn't a hang -- it is waiting for the task to complete.

Threading primitives in C++11 are primitives; they are not finished types for a full featured application. You can use them to write thread pools deferred task queues and the like; using them naively "in the raw" tends to result in them biasing towards correctness, but not giving you what you want.

A simple thread pool is just:

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;
};

Live example.

Now you create some thread pool somewhere, toss tasks at it, and you can wait on the futures in question. There is a bounded number of threads in the pool.

run_task will ensure there is a thread to run any task you queue. queue_task will use only existing threads if available.

The returned std::future<void> does not block for the task to complete; but the thread_pool object's destructor does.

Note that it will abort all queued tasks, and wait for currently runing tasks to complete, by default on destruction.

Something that wraps a unique_ptr<thread_pool> is useful to permit ease of moving. Moving has to be disabled because active threads keep a pointer-to-this.

thread_pool is not thread safe ironically; this is because we don't guard the std::vector<std::future<void>> threads;; I mean, other than thread safe with regard to the threads itself stores. It is designed to be directly accessed by only one external thread.

queue_task and terminate is thread safe mostly by accident.




回答4:


The reason for the error is because the compiler was not told that the result of your function DoModify() will be available asynchronously ( hence to be declared as std::future<> ) and it expected a synchronous result of bool type which didn't arrive so. You can use std::future::is_ready() or std::future_status . Here a sample code snippet

 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
     }
 }


来源:https://stackoverflow.com/questions/42280364/stdfuture-returned-from-stdasync-hangs-while-going-out-of-scope

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!