0

currently we are using asynchronous values very heavily. Assume that I have a function which does something like this:

int do_something(const boost::posix_time::time_duration& sleep_time)
{
    BOOST_MESSAGE("Sleeping a bit");
    boost::this_thread::sleep(sleep_time);
    BOOST_MESSAGE("Finished taking a nap");

    return 42;
}

At some point in code we create a task which creates a future to such an int value which will be set by a packaged_task - like this (worker_queue is a boost::asio::io_service in this example):

boost::unique_future<int> createAsynchronousValue(const boost::posix_time::seconds& sleep)
{
    boost::shared_ptr< boost::packaged_task<int> > task(
        new boost::packaged_task<int>(boost::bind(do_something, sleep)));
    boost::unique_future<int> ret = task->get_future();

    // Trigger execution
    working_queue.post(boost::bind(&boost::packaged_task<int>::operator (), task));

    return boost::move(ret);
}

At another point in code I want to wrap this function to return some higher level object which should also be a future. I need a conversion function which takes the first value and transforms it to another value (in our actual code we have some layering and doing asynchronous RPC which returns futures to responses - these responses should be converted to futures to real objects, PODs or even void future to be able to wait on it or catch exceptions). So this is the conversion function in this example:

float converter(boost::shared_future<int> value)
{
    BOOST_MESSAGE("Converting value " << value.get());
    return 1.0f * value.get();
}

Then I thought of creating a lazy future as described in the Boost docs to do this conversion only if wanted:

void invoke_lazy_task(boost::packaged_task<float>& task)
{
    try
    {
        task();
    }
    catch(boost::task_already_started&)
    {}
}

And then I have a function (might be a higher level API) to create a wrapped future:

boost::unique_future<float> createWrappedFuture(const boost::posix_time::seconds& sleep)
{
    boost::shared_future<int> int_future(createAsynchronousValue(sleep));
    BOOST_MESSAGE("Creating converter task");
    boost::packaged_task<float> wrapper(boost::bind(converter, int_future));

    BOOST_MESSAGE("Setting wait callback");
    wrapper.set_wait_callback(invoke_lazy_task);

    BOOST_MESSAGE("Creating future to converter task");
    boost::unique_future<float> future = wrapper.get_future();

    BOOST_MESSAGE("Returning the future");
    return boost::move(future);
}

At the end I want to be able to use it like this:

{    
    boost::unique_future<float> future = createWrappedFuture(boost::posix_time::seconds(1));
    BOOST_MESSAGE("Waiting for the future");
    future.wait();
    BOOST_CHECK_EQUAL(future.get(), 42.0f);
}

But here I end up getting an exception about a broken promise. The reason seems to be pretty clear for me because the packaged_task which does the conversion goes out of scope.

So my questing is: How do I deal with such situations. How can I prevent the task from being destroyed? Is there a pattern for this?

Bests,

Ronny

duselbaer
  • 935
  • 2
  • 6
  • 10
  • I thought of creating an own class which provides the same interface as boost::..._future does which holds the task and delegates all the interface calls to the future provided by the task. But there must be a better solution. – duselbaer Oct 25 '12 at 10:43
  • Is there a reason you can't wrap the function being evaluated so that the conversion is done when the task is invoked? – Dave S Oct 25 '12 at 11:26
  • @DaveS The main problem is that these functions are parts of different layers in our software. The createAsynchronousValue method corresponds to an RPC-Request which is managed by the RPC messaging layer. I will think about it ... :) – duselbaer Oct 25 '12 at 11:57

1 Answers1

0

You need to manage the lifetime of task object properly.

The most correct way is to return boost::packaged_task<float> instead of boost::unique_future<float> from createWrappedFuture(). The caller will be responsible to get future object and to prolongate task lifetime until future value is ready.

Or you can place task object into some 'pending' queue (global or class member) the similar way you did in createAsynchronousValue. But in this case you will need to explcitly manage task lifetime and remove it from queue after completion. So don't think this solution has advantages against returning task object itself.

Rost
  • 8,779
  • 28
  • 50
  • That's what I wanted to avoid – duselbaer Oct 25 '12 at 11:57
  • @duselbaer You cannot avoid object lifetime management, sorry. – Rost Oct 25 '12 at 12:33
  • Sure :) It seems that std::async provides a future which has the task to be executed bound to it. Unfortunately we don't have a modern compiler here... – duselbaer Oct 25 '12 at 12:54
  • But anyway your answer is correct and I already thought of doing it like this but I don't want the user of a higher level interface to get a task back where this task is more or less used as a future. – duselbaer Oct 25 '12 at 12:55
  • @duselbaer You always can encapsulate both future and task into some wrapper class with future-like interface. But not sure it will worth its price... – Rost Oct 25 '12 at 12:58