Skip to content

Customizing Poco::AbstractEvent::notifyAsync() Threading

Günter Obiltschnig edited this page Sep 18, 2017 · 1 revision

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;
}