5

Is there a way to implement timeout in the std::async method, so I want this call to timeout and complete if the thread hasnt completed for the specified amount of time. How can I implement this functionality.

Adobri
  • 471
  • 7
  • 15
  • 3
    Pass a starting time and max duration to the function, let it check at the start (and possibly as it goes) that the current time minus the starting time is less than the max duration. – GManNickG Jan 07 '13 at 20:40
  • @GManNickG: I dont want to rewrite the logic if any wrapper exists for doing this. – Adobri Jan 07 '13 at 20:51
  • 1
    There doesn't, but I wrote one for you anyway. :) – GManNickG Jan 07 '13 at 21:05

2 Answers2

6

The standard does not provide any way to non-cooperatively kill a thread. The lack of automatic timeout functionality is just another example of this.

Instead you must implement the code you pass to std::async to handle doing this cooperatively. One pattern for cooperatively killing a thread is to pass to the function an object which provides a method to check if the async function should continue, and if not it throws an exception.

struct Time_out {
    std::chrono::steady_clock start = std::chrono::steady_clock::now();
    std::chrono::milliseconds timeout;
    Time_out(std::chrono::milliseconds t) : timeout(t) {}

    void check() {
        if (start + timeout < std::chrono::steady_clock::now())
            throw timeout_exception();
    }
};

std::future<void> f = std::async([](Time_out t) {
    while(more work) {
        // do work
        t.check();
    }
}, Time_out(std::chrono::seconds(2));

f.get();
bames53
  • 86,085
  • 15
  • 179
  • 244
  • 3
    @Ali You can check as often as you like to ensure minimal delay. There's no alternative because you can't just kill threads (and if you could you shouldn't because it wouldn't be safe). – bames53 Jan 07 '13 at 21:13
6

There is no (standard) way to reach into a thread and kill it, and this is generally a bad idea anyway. The much cleaner option is to pass a starting time and max duration to the function and then (possibly multiple times as the calculation progresses) check if the current time minus the starting time is too long.

I would make something like this:

#include <chrono>

template <typename Clock = std::chrono::steady_clock>
class timeout
{
public:
    typedef Clock clock_type;
    typedef typename clock_type::time_point time_point;
    typedef typename clock_type::duration duration;

    explicit timeout(duration maxDuration) :
    mStartTime(clock_type::now()),
    mMaxDuration(maxDuration)
    {}

    time_point start_time() const
    {
        return mStartTime;
    }

    duration max_duration() const
    {
        return mMaxDuration;
    }

    bool is_expired() const
    {
        const auto endTime = clock_type::now();

        return (endTime - start_time()) > max_duration();
    }

    static timeout infinity()
    {
        return timeout(duration::max());
    }

private:
    time_point mStartTime;
    duration mMaxDuration;
};

This simple utility tracks the starting time and a max duration (and provides a way of specifying infinity), and allows the user to query simple facts, most importantly whether or not a timeout has occurred.

Test below; you can add fake delay by defining/undefining FAKE_DELAY:

#include <iostream>
#include <future>

#define FAKE_DELAY

void fake_delay()
{
    #ifdef FAKE_DELAY
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    #endif
}

void short_running_function(timeout<> timelimit)
{
    fake_delay();

    if (timelimit.is_expired())
        std::cout << "short running thread ran out of time" << std::endl;
    else
        std::cout << "short running function finished" << std::endl;
}

void long_running_function(timeout<> timelimit)
{
    for (unsigned i = 0; i < 10; ++i) {
        if (timelimit.is_expired())
        {
            std::cout << "long running thread ran out of time" << std::endl;
            return;
        }

        std::cout << "long running thread doing work" << std::endl;
        fake_delay();
    }

    std::cout << "long running function finished" << std::endl;
}

int main()
{
    std::async(short_running_function,
               timeout<>(std::chrono::milliseconds(500))).wait();

    std::async(short_running_function,
               timeout<>(std::chrono::milliseconds(5000))).wait();

    std::async(long_running_function,
               timeout<>(std::chrono::milliseconds(500))).wait();

    std::async(long_running_function,
               timeout<>(std::chrono::milliseconds(5000))).wait();

    std::async(long_running_function,
               timeout<>::infinity()).wait();
}

One possible output with FAKE_DELAY off:

short running function finished
short running function finished
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running function finished
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running function finished
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running function finished

One possible output with FAKE_DELAY on:

short running thread ran out of time
short running function finished
long running thread doing work
long running thread ran out of time
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread ran out of time
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running thread doing work
long running function finished

GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • 1
    I would like to know if the method passed to the std::async is lets say a blocking call(unlike the for loop in the example you gave, which can discretely check for expiration), then how do you perform the timeout. – Adobri Jan 18 '13 at 02:13
  • 1
    @Adobri: You don't. That blocking call needs to eventually return (or be called with a timeout of its own). – GManNickG Jan 18 '13 at 02:31