Runt8 Runt8 - 2 months ago 10
C++ Question

Call std::function in different thread

I'm working in a c++ framework, mostly written before c++11, that allows us to fire events from a thread to a different thread (assuming that the receiving thread is running an event queue, so it's mainly used to fire events to the main UI thread from a helper thread).

Currently, the code to accomplish this is very verbose - it requires us to define two classes:


  1. An event class that contains any information we want to transfer.

  2. A listener class that handles the event in the receiving thread.



We've recently moved to c++11/14 and have been working on updating a lot of our code to use smart pointers, standard containers, and lambdas. I'd like to write a generic class that allows me to send a lambda to be run in a different thread. Something like:

mBoundary = make_unique<ThreadBoundary>( []( int value ) { doSomething( value ); } );
mBoundary->callInMainThread( 47 );

ThreadBoundary boundary2( []( std::string value ) { displayString( value ); } );
boundary2.callInMainThreadWait( "Show this string to the user" );


As an initial attempt, I've currently got this working with a lambda that doesn't take any parameters, built on top of the current framework functionality (omitting error checking, cleanup, etc):

class ThreadBoundary
{
public:
ThreadBoundary( std::function<void()> function ):mFunction( function )
{
mListener = make_shared<ThreadBoundaryListener>();
cApplication::addThreadBoundaryListener( mListener );
}

void callInMainThread()
{
cApplication::fireEventInMainThread( new ThreadBoundaryEvent( mFunction ) );
}

class ThreadBoundaryEvent:public FrameworkThreadBoundaryEvent
{
public:
ThreadBoundaryEvent( std::function<void()> function )
{
mFunction = function;
}

void call() { mFunction(); }

private:
std::function<void()> mFunction;
};

class ThreadBoundaryListener:public FrameworkThreadBoundaryListener
{
public:
ThreadBoundaryListener() {}

void handleEvent( const FrameworkThreadBoundaryEvent* event )
{
dynamic_cast<const ThreadBoundaryEvent*>( event )->call();
}
};

private:
shared_ptr<ThreadBoundaryListener> mListener;
std::function<void()> mFunction;
};


This allows me to fire "one-off" events to the main thread, however, without being able to send along parameters it's functionality is pretty limited.

I'd like to make this class use variadic templates so that I can pass anything to the
callInMainThread
function. However, I haven't been able to figure out how to store the parameter pack in the event and pass it along to the function inside
call()
. So my questions are:


  1. Is it possible to store a parameter pack and later pass it to a std::function?

  2. Is there a better way to do this in c++11/14 that doesn't require a huge redesign? Our framework is currently wrapping OS functionality to accomplish this (i.e. on Windows it uses SendMessage vs performSelectorOnMainThread in OS X).


Answer

Instead of separating the interface construction from the invocation, use the power of the lambdas to capture arguments into the function object itself (a closure) so that every invocation is the same. Don't have the basic thread communication object encapsulate a function type. Instead, pass the entire function as you call. Go for this interface:

threadBoundary.callInMainThread([value](){doSomething(value);});

If everything you pass is a function object that takes no parameters, it's easy to pass and easy to call. Capture anything you need to pass.

For some interface and implementation ideas, look at how boost::asio implements its io_service, which allows you to post function calls to other threads just like this.