-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Customizing Poco::AbstractEvent::notifyAsync() Threading
A customer came up with an interesting requirement: using a custom Poco::ThreadPool
instance for Poco::AbstractEvent::notifyAsync()
. notifyAsync()
is implemented using Poco::ActiveMethod
, which again uses an ActiveStarter
policy class to actually start the method in a different thread.
I first thought about adding support to provide a Poco::ThreadPool
object to Poco::BasicEvent
through its constructor, but this turned out not feasible due to the significant changes required (to the whole events and active object framework). Also, probably not very generic and elegant.
After some deeper thinking I came up with a quite elegant solution that does not require any changes to any POCO classes.
The trick is to use template specialization. As mentioned before, Poco::AbstractEvent
(the base class template for Poco::BasicEvent
) uses the Poco::ActiveMethod
template to implement notifyAsync()
.
One of the template parameters of Poco::ActiveMethod
is the ActiveStarter
policy, which is used to start the thread for the active method. The default implementation (in Poco/ActiveStarter.h
) using the global default ThreadPool
instance looks as follows:
template <class OwnerType>
class ActiveStarter
{
public:
static void start(OwnerType* /*pOwner*/, ActiveRunnableBase::Ptr pRunnable)
{
ThreadPool::defaultPool().start(*pRunnable);
pRunnable->duplicate(); // The runnable will release itself.
}
};
Now, it turns out that this is C++ and in C++ you can specialize templates to your heart's content. In this case, we specialize the ActiveStarter
template for Poco::AbstractEvent
(the OwnerType
). Therefore, every Poco::AbstractEvent
instance will use our custom specialization instead of the default one. We just have to make sure that our template specialization is visible to every class using Poco::AbstractEvent
or Poco::BasicEvent
.
So, the specialization for ActiveStarter
using a custom Poco::ThreadPool
instance basically looks like this:
namespace Poco
{
template <class TArgs, class TStrategy, class TDelegate, class TMutex>
class ActiveStarter<AbstractEvent<TArgs, TStrategy, TDelegate, TMutex> >
{
public:
static void start(AbstractEvent<TArgs, TStrategy, TDelegate, TMutex>* pOwner, ActiveRunnableBase::Ptr pRunnable)
{
eventThreadPool().start(*pRunnable);
pRunnable->duplicate(); // The runnable will release itself.
}
};
};
What you also need is a global function Poco::ThreadPool& eventThreadPool()
that returns the instance of the Poco::ThreadPool
you’ll want to use.
Put this specialization into a header file and #include
it in every class declaration that uses Poco::BasicEvent
. No further code changes to existing code are required.
A complete proof-of-concept is below:
#include "Poco/ActiveStarter.h"
#include "Poco/BasicEvent.h"
#include "Poco/Delegate.h"
#include "Poco/ThreadPool.h"
#include <iostream>
Poco::ThreadPool& eventThreadPool()
{
static Poco::ThreadPool pool(2, 16, 30);
return pool;
}
namespace Poco
{
template <class TArgs, class TStrategy, class TDelegate, class TMutex>
class ActiveStarter<AbstractEvent<TArgs, TStrategy, TDelegate, TMutex> >
{
public:
static void start(AbstractEvent<TArgs, TStrategy, TDelegate, TMutex>* pOwner, ActiveRunnableBase::Ptr pRunnable)
{
std::cout << "ActiveStarter<AbstractEvent>" << std::endl;
eventThreadPool().start(*pRunnable);
pRunnable->duplicate(); // The runnable will release itself.
}
};
};
class EventSource
{
public:
Poco::BasicEvent<void> event;
};
void callback()
{
std::cout << "CALLBACK!" << std::endl;
}
int main(int argc, char** argv)
{
EventSource src;
src.event += Poco::delegate(callback);
src.event.notifyAsync(0);
Poco::Thread::sleep(1000);
return 0;
}