Task Scheduler

Started by
16 comments, last by void0 12 years, 6 months ago
I can't see why you're holding on to a copy of io_service::work in your task_scheduler class.
Advertisement

I can't see why you're holding on to a copy of io_service::work in your task_scheduler class.


So io_service::run won't exit when it runs out of tasks to process. I've never used Boost.Asio before so maybe it is not needed.

Edit: In fact, if io_service::work goes out of scope, then io_service::work_finished() is called which will terminate the worker thread when it runs out of tasks.

See impl/io_service.hpp:

[source]
inline io_service::work::work(boost::asio::io_service& io_service)
: io_service_(io_service)
{
io_service_.impl_.work_started();
}

inline io_service::work::work(const work& other)
: io_service_(other.io_service_)
{
io_service_.impl_.work_started();
}

inline io_service::work::~work()
{
io_service_.impl_.work_finished();
}
[/source]

Or am I wrong?
I've used boost.asio three times and never had to touch the work class. The documentation is a little misleading in its wording; what the work class does is prevents the run function from returning when you call stop IIRC.

But then, I've only used asio for the asio part; not the task part (well, I did wind up using the task part, but you know what I mean). So maybe it's different when you're not listening for a connection.

I've used boost.asio three times and never had to touch the work class. The documentation is a little misleading in its wording; what the work class does is prevents the run function from returning when you call stop IIRC.

But then, I've only used asio for the asio part; not the task part (well, I did wind up using the task part, but you know what I mean). So maybe it's different when you're not listening for a connection.


I think you are wrong. Maybe your io_service actually had always "work" to do (periodic timer?). Here is the documentation for io_service::work that I found in the io_service.hpp header file:

* @par Stopping the io_service from running out of work
*
* Some applications may need to prevent an io_service object's run() call from
* returning when there is no more work to do. For example, the io_service may
* be being run in a background thread that is launched prior to the
* application's asynchronous operations. The run() call may be kept running by
* creating an object of type boost::asio::io_service::work:
*
* @code boost::asio::io_service io_service;
* boost::asio::io_service::work work(io_service);
* ... @endcode
*
* To effect a shutdown, the application will then need to call the io_service
* object's stop() member function. This will cause the io_service run() call
* to return as soon as possible, abandoning unfinished operations and without
* permitting ready handlers to be dispatched.[/quote]

Edit: I read your answer too quickly. Yes, listening on a connection is different. This qualifies as "work" and will prevent io_service::run from exiting. In my case with a thread pool and tasks posted through io_service::post(), I must keep the io_service busy when there are no more tasks to run.
Alright then, that makes sense!
On the topic of io_service and work, should we be calling io_service::stop? Does it kill the threads abruptly or does it let them finish properly? The doco mentions this:



To effect a shutdown, the application will then need to call the io_service object's stop() member function. This will cause the io_service run() call to return as soon as possible, abandoning unfinished operations and without permitting ready handlers to be dispatched.

Alternatively, if the application requires that all operations and handlers be allowed to finish normally, the work object may be explicitly destroyed.
[font="sans-serif"][source lang=cpp][/font]
[font="sans-serif"]
boost::asio::io_service io_service;
auto_ptr<boost::asio::io_service::work> work(
new boost::asio::io_service::work(io_service));
...
work.reset(); // Allow run() to exit.
[/source][/font]
[/quote]

As for your recent changes void0, that is definitely much nicer than using the shared pointer. I did however notice a problem with combining the Task and TaskNotify classes and that is you can't have a task that returns void. This would be a problem with TaskNotify too though if you tried using void as return type.

The compiler will complain at the point of calling the callback with:


error C2064: term does not evaluate to a function taking 1 arguments
[/quote]

I'm not really a boost guru so I'm not sure how or if we can get around this. Maybe there's some other magic to use with boost::function and templates heh.

[quote name='nfries88' timestamp='1318360947' post='4871557']
I've used boost.asio three times and never had to touch the work class. The documentation is a little misleading in its wording; what the work class does is prevents the run function from returning when you call stop IIRC.

But then, I've only used asio for the asio part; not the task part (well, I did wind up using the task part, but you know what I mean). So maybe it's different when you're not listening for a connection.


I think you are wrong. Maybe your io_service actually had always "work" to do (periodic timer?). Here is the documentation for io_service::work that I found in the io_service.hpp header file:

* @par Stopping the io_service from running out of work
*
* Some applications may need to prevent an io_service object's run() call from
* returning when there is no more work to do. For example, the io_service may
* be being run in a background thread that is launched prior to the
* application's asynchronous operations. The run() call may be kept running by
* creating an object of type boost::asio::io_service::work:
*
* @code boost::asio::io_service io_service;
* boost::asio::io_service::work work(io_service);
* ... @endcode
*
* To effect a shutdown, the application will then need to call the io_service
* object's stop() member function. This will cause the io_service run() call
* to return as soon as possible, abandoning unfinished operations and without
* permitting ready handlers to be dispatched.[/quote]

Edit: I read your answer too quickly. Yes, listening on a connection is different. This qualifies as "work" and will prevent io_service::run from exiting. In my case with a thread pool and tasks posted through io_service::post(), I must keep the io_service busy when there are no more tasks to run.
[/quote]

I never run into this problem because I always keep a "heartbeat" timer going. It pretty much doesn't matter what I'm doing, I want some form of "yes, I'm still working" sort of status printf'd, logged, sent to sockets, whatever. Given boost asio, the best "yes, I'm still alive" method is for the "main" function to maintain a deadline timer at all times. If you start more threads to work on the queue, each should maintain it's own heartbeat timer to make sure it hasn't hung up for some reason.

Without this, it is easy to end up with a hung thread doing who knows what using a full core because it is trashed but leaving the other threads working and everything is happy. As more threads crash you "may" notice the problem fairly late and by which time your server is severely hosed.

As for your recent changes void0, that is definitely much nicer than using the shared pointer. I did however notice a problem with combining the Task and TaskNotify classes and that is you can't have a task that returns void. This would be a problem with TaskNotify too though if you tried using void as return type.

The compiler will complain at the point of calling the callback with:


error C2064: term does not evaluate to a function taking 1 arguments


I'm not really a boost guru so I'm not sure how or if we can get around this. Maybe there's some other magic to use with boost::function and templates heh.
[/quote]

You can get around this with full template specialization. If you can stomach the duplicated code, that is. I couldn't quite figure out how to put the common code into a base class as I get compile errors trying to call a templated base class constructor through a templated sub class constructor (Is it even possible?). You see, I'm no template/boost guru either, but it gets the job done:

[source lang=cpp]
template <typename R>
class task_base {
public:
typedef boost::packaged_task<R> task_type;
typedef boost::shared_ptr<task_type> task_ptr;
typedef boost::shared_future<R> future_type;
typedef boost::function<void (R)> callback_type;
};

template <typename R>
class task : public task_base<R> {
public:
template <typename F>
task(F f)
: ptask(boost::make_shared<task_type>(f)),
future(ptask->get_future())
{}

template <typename F>
task(F f, callback_type cb)
: ptask(boost::make_shared<task_type>(f)),
future(ptask->get_future()),
callback(cb)
{}

void operator()()
{
(*ptask)();
if(!callback.empty()) callback(get());
}

future_type get_future()
{ return future; }

R get()
{ return future.get(); }

bool ready() const
{ return future.is_ready(); }

private:
task_ptr ptask;
future_type future;
callback_type callback;
};

template <>
class task<void> : public task_base<void> {
public:
template <typename F>
task(F f)
: ptask(boost::make_shared<task_type>(f)),
future(ptask->get_future())
{}

template <typename F>
task(F f, callback_type cb)
: ptask(boost::make_shared<task_type>(f)),
future(ptask->get_future()),
callback(cb)
{}

void operator()()
{
(*ptask)();
if(!callback.empty()) callback();
}

// This one really does not make sense, force a compile error?
//void get() {}

future_type get_future()
{ return future; }

bool ready() const
{ return future.is_ready(); }

private:
task_ptr ptask;
future_type future;
callback_type callback;
};
[/source]

This topic is closed to new replies.

Advertisement