Introduction
There are a lot of applications written in MFC that could benefit greatly from using available libraries like boost and tbb, while we are trying to scale them up, making them take advantage of the multiple cores available on modern platforms.
While we are supporting, migrating, improving an MFC application we think how nice it would be, to be able to call certain methods of existing classes asynchronously, so that we can free the UI thread of some of the burden, simplify implementation of time-consuming algorithms in a single-threaded UI app, or just be able to do more.
One last introductory mention would be that we intentionally left out a lot or some could argue all of the theoretical (or worse philosophical) aspects, details, nomenclatures or design patterns paradigms, and rigors of coding, concentrating in providing something very accessible, easy to read and understand, something practical.
Background
As soon as we try to implement asynchronous execution, all sorts of road blocks start popping up like mushrooms after rain. MFC objects have thread affinity, they are not thread safe, some of them are using a COM object inside and insist in being called from a single threaded apartment (which most often means the main UI thread) . We need to pump messages in between execution of our calls to cater the needs of the UI, COM or
Winsock (async). We need to marshal calls, package parameters and return values. We have to worry about thread safety and minimal locking between threads, catch exceptions and pass them properly back to the caller.
A whole other level of challenges come if the class we would like to run asynchronously calls back, like observable objects or COM/ActiveX objects with connection points.
Overwhelming isn't it ? How badly we wish to just be able to port this code to .Net and abuse the built-in async pattern ?
The goal of this article is to illustrate a possible method for turning an existing MFC class (with all it's constraints) from a synchronous executing class into a asynchronously executing one, without changing the implementation of that class.
One very important thing for achieving this goal would be to have the class properly designed, interfaces defined, callback interfaces if that's the case.
Having interfaces allows us to implement alternative classes that forward the actual call to the original class, while the actual execution happens in a separate background thread.
And since real life can offer it, let's say that he class we would like to execute asynchronously has methods we want to execute synchronously, methods that return results synchronously, and methods we are executing async and firing callbacks upon completion.
Using the code
We'll have to start with an apology to the reader, as the sample application might not be a very inspired one. We had to choose between doing something that would illustrate the use of boost and tbb in implementing a mechanism or method for calling methods asynchronously and not doing anything at all. So we did something we hope you will find useful.
The code was written Visual Studio 2008 SP1 with TR1, is using boost 1.49 statically linked and tbb 4.0 from 6/13/2012. The archive should have everything needed for the code to compile in both release and debug.
We have a class called CCalculator
which implements the interface called
ICalculator
and fires events when it needs to using the ICalculatorEvents
interface it receives in the constructor:
class ICalculator
{
public:
virtual bool Start(void) = 0;
virtual int Add(int a, int b) = 0;
virtual void Factorial(unsigned int a) = 0;
virtual void Stop(void) = 0;
};
class ICalculatorEvents
{
public:
virtual void Started(void) = 0;
virtual void Stopped(void) = 0;
virtual void Result(int) = 0;
virtual void Error(int) =0;
};
class CCalculator :
public ICalculator
{
public:
CCalculator(ICalculatorEvents&);
~CCalculator(void);
bool Start(void);
int Add(int a, int b);
void Factorial(unsigned int a);
void Stop(void);
protected:
bool m_running;
ICalculatorEvents& m_callback;
};
CCalculator::CCalculator(ICalculatorEvents& callback):
m_running(false),
m_callback(callback)
{
}
CCalculator::~CCalculator(void)
{
}
bool CCalculator::Start(void)
{
m_running = true;
m_callback.Started();
return true;
}
int CCalculator::Add(int a, int b)
{
return a+b;
}
void CCalculator::Factorial(unsigned int a)
{
int factorial = (a == 0)?0:1;
for (unsigned int i = 1; i < a; i++)
factorial *= i;
m_callback.Result(factorial);
}
void CCalculator::Stop(void)
{
m_running = false;
m_callback.Stopped();
}
We use the CCalculator
class inside the CMainDlg
class where we make an instance of it or the alternative implementation provided by the
CAsyncCalculator
. Having an interface removes the need to change the user code.
BOOL CMainDlg::OnInitDialog() {
...
m_calculator = boost::shared_ptr<ICalculator>(new CAsyncCalculator(*this));
return TRUE; }
The alternative implementation of ICalculator
interface, the one that executes the methods and the callbacks asynchronously is called
CAsyncCalculator
defined as follows:
typedef tbb::concurrent_queue<boost::function<void()>> CallQueue;
class CEventReceiver:public CWnd
{
public:
CEventReceiver(CallQueue& queue);
~CEventReceiver();
void QueueCall(boost::function<void()> &call);
protected:
LRESULT OnExecuteCall(WPARAM, LPARAM);
DECLARE_MESSAGE_MAP()
private:
CallQueue& m_call_queue;
};
class CAsyncCalculator :
public CWinThread,
public ICalculator,
private ICalculatorEvents
{
public:
CAsyncCalculator(ICalculatorEvents&);
~CAsyncCalculator(void);
bool Start(void);
int Add(int a, int b);
void Factorial(unsigned int a);
void Stop(void);
protected:
afx_msg void OnExecuteCall(WPARAM, LPARAM);
DECLARE_MESSAGE_MAP()
void QueueCall(boost::function<void()> &call);
private:
BOOL InitInstance();
int ExitInstance();
void Started(void);
void Stopped(void);
void Result(int);
void Error(int);
private:
ICalculatorEvents& m_external_callback;
boost::shared_ptr <ICalculator> m_calculator_impl;
CallQueue m_call_queue;
CallQueue m_callback_queue;
boost::barrier m_worker_thread_started;
CEventReceiver m_event_receiver;
};
Points of Interest
Before I forget, let me say that when you use multiple inheritance in MFC, you better put the MFC class as the first one, if you don't want any surprises:
warning C4407: cast between different pointer to member representations, compiler may generate incorrect code
While compiling this line:
ON_THREAD_MESSAGE(UWM_EXECUTE_CALL, OnExecuteCall)
CAsyncCalculator
is a class derived from the ICalculator
interface. We also derive it from
CWinThread
and Create a background thread in the constructor. This thread will execute our calls. We have a concurrent queue for the calls we need to execute on the background thread and another one for the callbacks. The callbacks need to be executed by the same thread that is calling the methods of the
ICalculator
interface, the same one that created the instance of
CAsyncCalculator
. This is where the CEventReceiver
window class comes into picture, it is a message handling window created in the constructor of
CAsyncCalculator
that takes the callbacks and executes them.
CAsyncCalculator::CAsyncCalculator(ICalculatorEvents& callback):
m_external_callback(callback),
m_worker_thread_started(2),
m_event_receiver(m_callback_queue)
{
BOOL thread_created = CreateThread();
m_worker_thread_started.wait();
}
We admit that this CAsyncCalculator
class is a bit jammed with too many things and it does have methods that are called from two different threads, but please take it as an educational example rather than something you might want to submit for a rigorous OOP code review.
It is important to have the instance of the contained CCalculator
object created by the background thread, where all the calls
to the CCalculator
instance are going to be executed to, and all the callbacks are going to be fired from.
BOOL CAsyncCalculator::InitInstance()
{
m_calculator_impl = boost::shared_ptr<ICalculator>(new CCalculator(*this));
m_worker_thread_started.wait();
return TRUE;
}
The implementation of ICalculator
interface in CAsyncCalculator
class reveals the mechanisms through which the calls are packaged and marshaled, queued to be executed on a different thread.
bool CAsyncCalculator::Start(void)
{
boost::packaged_task<bool> task(boost::bind(&ICalculator::Start,m_calculator_impl.get()));
boost::unique_future<bool> f = task.get_future();
QueueCall(MoveTaskIntoFunction<bool>(task));
if (!f.timed_wait(CALL_TIMEOUT))
{
throw std::runtime_error("Start call timeout.");
}
return f.get();
}
Above you see that we want to execute the call in a sync fashion but have a timeout as a maximum duration we afford to wait for the execution to complete.
MoveTaskIntoFunction
is a whole story in itself and we credit D Drmmr for coming up with it. You can read more about the subject here. You needed because you want to store in the same concurrent queue tasks/functions that have different return types.
QueueCall
and
OnExecuteCall
looked like this:
void CAsyncCalculator::QueueCall(boost::function<void()> &call)
{
m_call_queue.push(call);
PostThreadMessage(UWM_EXECUTE_CALL, 0, 0);
}
void CAsyncCalculator::OnExecuteCall(WPARAM, LPARAM)
{
boost::function<void()> call;
while(m_call_queue.try_pop(call))
{
call();
}
}
The callbacks are going through a similar hijacking process where the initial callback interface reference is being stored as a member variable
m_external_callback
locally and replaced with a a private implementation provided by the inheritance of the
ICalculatorEvents
.
Once the callback makes it from
CCalculator
to CAsyncCalculator
is being packaged and queued for execution by the
CEventReceiver
object.
void CAsyncCalculator::Started(void)
{
boost::packaged_task<void> task(boost::bind(&ICalculatorEvents::Started, &m_external_callback));
m_event_receiver.QueueCall(MoveTaskIntoFunction<void>(task));
}
This happens in the OnExecuteCall
message handling method similarly with what we had in
CAsyncCalculator
:
LRESULT CEventReceiver::OnExecuteCall (WPARAM, LPARAM)
{
boost::function<void()> call;
while(m_call_queue.try_pop(call))
{
call();
}
return NULL;
}
void CEventReceiver::QueueCall(boost::function<void()> &call)
{
m_call_queue.push(call);
::PostMessage(m_hWnd, UWM_EXECUTE_CALL, 0,0);
}
It might look like a lot of code to type, but I'm sure there could be ways to shorten that.