4

I'm trying to construct a work queue of functions that need to be executed by one thread and can be fed by many threads. To accomplish this, I was planning on using the boost::packaged_task and boost::unique_future. The idea would be you would do:

Foo value = queue.add(myFunc).get();

which would block, until the function is executed. So queue.add(...) takes in a boost::function, and returns a boost::unique_future. Internally it then creates a boost::packaged_task using the boost::function for its constructor.

The problem I'm running into is that boost::function<...> won't be the same every time. Specifically, the return value for it will change (the functions, however, will never take any parameters). Thus, I have to have an add function that looks something like:

template <typename ResultType>
boost::unique_future<ResultType> add(boost::function<ResultType ()> f) {
   boost::packaged_task<boost::function<ResultType ()> > task(f);
   queue.push_back(task);
   return task.get_future();
}

Okay, that doesn't seem too bad, but then I ran into the problem of how to define 'queue'. I think I have no choice but to use boost::any, since the types will not be constant:

std::list<boost::any> queue; // note: I'm not concerned with thread-safety yet

But then I run into a problem when I try to implement my executeSingle (takes just a single item off the queue to execute):

void executeSingle() {
    boost::any value = queue.back();
    boost::packaged_task<?> task = boost::packaged_task<?>(boost::move(value));
    // actually execute task
    task();
    queue.pop_back();
}

The '?' denote what I'm unsure about. I can't call executeSingle with a template, as it's called from a separate thread. I tried using boost::any, but I get the error:

  conversion from 'boost::any' to non-scalar type  boost::detail::thread_move_t<boost:thread>' requested.

The funny part is, I actually don't care about the return type of packaged_task at this point, I just want to execute it, but I can figure out the template details.

Any insight would be greatly appreciated!

Abe Schneider
  • 977
  • 1
  • 11
  • 23
  • The error stems from the fact that you're are trying to construct a `packaged_task` out of an `any` when you should really be using `boost::any_cast`. But see my answer below. – Luc Danton May 05 '11 at 04:15

3 Answers3

5

You should store boost::function<void()>'s. Note that boost::packaged_task<R>::operator() doesn't return anything; it populates the associated boost::future. In fact, even if it returned something you could still use boost::function<void()> since you'd still have no interest in the returned value: all you care about is to call queue.back()(). If this were the case boost::function<void()>::operator() would take care of discarding the returned value for you.

As a minor note, you might want to change the signature of your add method to be templated on a generic type Functor rather than a boost::function, and use boost::result_of to get the result type for boost::packaged_task.

My suggestion as a whole:

template<typename Functor>
boost::future<typename boost::result_of<Functor()>::type>
queue::add(Functor functor) // assuming your class is named queue
{
    typedef typename boost::result_of<Functor()>::type result_type;
    boost::packaged_task<result_type> task(functor);
    boost::unique_future<result_type> future = task.get_future();
    internal_queue.push_back(boost::move(task)); // assuming internal_queue member
    return boost::move(future);
}

void
queue::executeSingle()
{
    // Note: do you really want LIFO here?
    queue.back()();
    queue.pop_back();
}

EDIT

How to take care of move-semantics inside queue::add

typedef typename boost::result_of<Functor()>::type result_type;
typedef boost::packaged_task<result_type> task_type;
boost::shared_ptr<task_type> task = boost::make_shared<task_type>(functor);
boost::unique_future<result_type> future = task->get_future();

/* boost::shared_ptr is possibly move-enabled so you can try moving it */
internal_queue.push_back( boost::bind(dereference_functor(), task) );

return boost::move(future);

where dereference_functor could be:

struct dereference_functor {
    template<typename Pointer>
    void
    operator()(Pointer const& p) const
    {
        (*p)();
    }
};

You could also substitute the bind expression for the much clearer

boost::bind(&task_type::operator(), task)

which also doesn't require a custom functor. However if there are multiple overloads of task_type::operator() this might need disambiguation; the code could also break if a future change in the Boost.Thread introduce an overload.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • While I can't get it to work yet, this looks good! One thing I noticed is that I think the second line should have '::type' after the '>'. Two issues I ran against: (1) I don't seem to have future, only unique_future, and (2) the push_back and return both complain that unique_future's constructor is private. For the push_back, I added a boost::move(task), which solved that problem, but that doesn't seem to work for the return. – Abe Schneider May 06 '11 at 22:41
  • @Abe Schneider Thanks for pointing out the errors - `future` is part of `std`, not `boost`! The 'private' constructors really means that the classes are not copyable, and moving them was the right thing to do. I don't know however what's up with the return. You can change the `unique_future` to `shared_future` as a workaround. – Luc Danton May 06 '11 at 23:09
  • Apparently I typed too soon. I was incorrect, and the error was actually because of: "internal_queue.push_front(boost::move(task));" with the error "/usr/local/include/boost/function/function_template.hpp:153: error: no match for call to '(boost::detail::thread_move_t >) ()'". Could it be because my internal queue as: "std::list > internal_queue"? – Abe Schneider May 07 '11 at 02:48
  • That could be it; try `internal_queue.push_front( boost::function(boost::move(task)) );`. However you might run into problems further down the line since you would need move semantics for the `priority_queue` to properly shuffle `boost::function` around (and that's assuming `boost::function` is move-enabled...). – Luc Danton May 07 '11 at 02:57
  • Unfortunately using 'boost::function' doesn't seem to work either. I don't see 'thread_move_t' declared in boost::function, which I'm guessing means it's not moveable, which I'm guessing is the root of the problem. I'm tempted at this point to just write my own future class, since it's simple enough (and what I had originally intended to do before trying boost's version). Unfortunately I'm using gcc 4.2, which might also be part of the problem, and can't easily switch right now.. – Abe Schneider May 07 '11 at 16:41
  • @Abe Schneider Move semantics are hard in C++03, see my edit for a typical solution. – Luc Danton May 08 '11 at 01:37
  • Well, it almost works! The return is causing strange problems now. I'm getting: /Users/abes/work/COASTER/WB/software/trunk/gui/ogrequeue.h:43: error: no matching function for call to 'boost::unique_future::unique_future(boost::unique_future)' and among the candidates it lists are: /usr/local/include/boost/thread/future.hpp:658: candidates are: boost::unique_future::unique_future(boost::detail::thread_move_t >) [with R = int]. Since R is an int, I'm starting to get the feeling it's GCC 4.2's fault.. – Abe Schneider May 09 '11 at 17:39
  • @Abe The error message suggests to me that the move emulation is simply not working. I am not familiar about Boost.Move myself so I don't know if what you are trying to do is possible. You can return a `shared_future` to dodge move semantics altogether. – Luc Danton May 10 '11 at 01:41
  • Sadly task->get_future() returns a unique_future, and the only way I can see to initialize a shared_future is with boost::move. argh. Thanks for the help .. I think at this point I'm better off either trying to upgrade my version of GCC (which is difficult) or rolling my own (which is easier). I'm marking this solution as solved, as I'm sure if I had the proper GCC it would work. Thanks! – Abe Schneider May 10 '11 at 20:36
1

You use old-fashioned virtual functions. Define a base class task_base with a virtual execute method, then define a template derived class which holds a specific task instance. Something along the lines:

struct task_base {
  virtual void execute() = 0;
};
template<typename ResultType>
struct task_holder : task_base {
  task_holder(boost::packaged_task<boost::function<ResultType ()> >&& task)
    : m_task(task) { }
  void execute() {
    m_task();
  }
private:
  boost::packaged_task<boost::function<ResultType ()> > m_task;
};

And define your queue to hold unique_ptr<task_base>. This is essentially what boost::any does, only you'd be using a specific function, namely execute.

NOTE: Untested code! And I'm still not very familiar with rvalue references. This is just to give you the idea of how the code would look.

Pablo
  • 8,644
  • 2
  • 39
  • 29
  • Hmm. I tried this approach, but couldn't get it to work, on account of having issues with unique_future's constructor being private. If I understand correctly, boost::move is supposed to be used, but it doesn't work for the return value. Also, unfortunately, my current compiler (Apple's default) doesn't support rval references. It might be time to switch compilers... – Abe Schneider May 06 '11 at 22:38
0

Somewhat belatedly, but you might want to consider using Boost.Asio instead of rolling your own queue-runner solution.

While this grew up as an I/O library it does support asynchronous calls just like this. Simply define an io_service somewhere, run it inside a thread, and then post functors to get called on that thread.

Miral
  • 12,637
  • 4
  • 53
  • 93