0

I want to implement something like Java's TimerTask in C++. I want to use it for invoking functions sometimes, not periodic. For periodic launching it will be a good idea to implement "event loop" scheme with 2 threads, with creating tasks in the first thread and process it in the second. But I do not want to write much code. So I've written smth like this:

template <typename F, typename... Args>
auto timed_run(const uint64_t delay_ms, const F& function, Args... args) {
  const auto f = [&] {
    std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
    function(args...);
  };

  auto future = std::async(std::launch::async, f);
  return future;
}

But it does not work as I need because it is not asynchronous at all due to it waits at future destructor as described there.

So we need to create thread by ourselves. Okay, lets do it:

template <typename F, typename... Args>
auto timed_run(const uint64_t delay_ms, const F& function, Args... args) {
  std::packaged_task<void()> task([&]() {
    std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
    function(args...);
  });

  auto future = task.get_future();
  std::thread thread(std::move(task));
  thread.detach();

  return future;
}

In this implementation the are no locks and waits, but it simply does not run our function. It is because we can't use sleep on the detached threads.

So, how can I implement what i want?

genpfault
  • 51,148
  • 11
  • 85
  • 139
borune
  • 548
  • 5
  • 21
  • 2
    There is no problem with waiting for a destructor in the first version. The future is stored in whatever you assign the result of `timed_run` to. If you are discarding the result. then you do have a problem but that is a usage issue. But it does have a problem with capturing everything by reference due to `[&]`. The arguments and `delay_ms` go out of scope when the function returns, so there is a race between the asynchronous function using those objects and the outer function returning before then, which has Undefined Behavior. – François Andrieux Jul 26 '22 at 19:17

2 Answers2

0
template <typename F, typename... Args>
auto timed_run(const uint64_t delay_ms, F&& function, Args&&... args) {
  std::packaged_task<void()> task([=]() {
    std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
    function(args...);
  });

  auto future = task.get_future();
  std::thread(std::move(task)).detach();

  return future;
}
borune
  • 548
  • 5
  • 21
0

You can have your timed_run function launch an async task and return a future. At the callee point, just wait for the async task to complete.

[Demo]

#include <chrono>
#include <cstdint>  // uint64_t
#include <fmt/core.h>
#include <future>  // async
#include <thread>  // this_thread

template <typename F, typename... Args>
auto timed_run(const std::uint64_t delay_ms, F&& f, Args&&... args) {
    return std::async(std::launch::async, [=](){
        std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
        f(args...);
    });
}

int main() {
    using namespace std::chrono_literals;
    auto print_dots = []() {
        for (int i{4}; i > 0; --i) {
            fmt::print(".\n");
            std::this_thread::sleep_for(10ms);
        }
    };
    auto print_square = [](int n) { fmt::print("{}\n", n*n); };
    auto f1{ timed_run(0, print_dots) };
    auto f2{ timed_run(20, print_square, 2) };
    f1.get();
    f2.get();
}

// Outputs something like:
//
//   ..4..
rturrado
  • 7,699
  • 6
  • 42
  • 62
  • Thanks, good decision, but what if i do not want to wait until async operation complete? In ur case i have to save future somewhere for increasing its lifetime. Using of timed_task instead of std::async seems takes this problem away – borune Jul 28 '22 at 14:35
  • The example I wrote indeed have a main thread waiting for two other tasks to finish. You could indeed launch those two other tasks as threads (instead of async calls) and detach them (see [demo](https://godbolt.org/z/Gzs6fvxY3)) but, in that case, I would take special care of not [exiting the main application before the detached threads finish](https://stackoverflow.com/q/19744250/260313). Otherwise, the still running threads will be terminated (see [demo](https://godbolt.org/z/M65r7Wrv9)). – rturrado Jul 28 '22 at 21:57
  • But in that demo i cannot determine if async task finished or not. I think that packaged_task is better because it takes us more flexibility: if you want to inspect completion of async task you can do it with the result of timed_run, else you can simply ignore the result – borune Jul 29 '22 at 18:47