Remote Call Framework 3.0
Asynchronous Remote Calls

Asynchronous Invocation

Asynchronous invocation of remote calls allow you to initate remote calls on one thread, and have them complete asynchronously on another thread.

Asynchronous invocation is implemented in RCF using the RCF::Future<> class. If a RCF::Future<> object is supplied as the return value, or as one of the parameters, of a remote call, the remote call is performed asynchronously.

For example, given this RCF interface:

RCF_BEGIN(I_PrintService, "I_PrintService")
// Returns number of characters printed.
RCF_METHOD_R1(int, Print, const std::string &)
RCF_END(I_PrintService)

, an asynchronous call would be initiated by the following code:

RcfClient<I_PrintService> client(( RCF::TcpEndpoint(port) ));
RCF::Future<int> fRet = client.Print("Hello World");

The thread that executes this code will return immediately. The code can subsequently poll for completion:

// Poll until remote call completes.
while (!fRet.ready())
{
RCF::sleepMs(500);
}

, or it can wait for completion:

// Wait for remote call to complete.
fRet.wait();

Once the call is complete, the return values can be recovered from the relevant Future<> instances:

int charsPrinted = *fRet;

If the call resulted in an error, the error will be thrown when the Future<> instance is dereferenced, as above. Alternatively, the error can be retrieved by calling RCF::Future<>::getAsyncException():

std::unique_ptr<RCF::Exception> ePtr = fRet.getAsyncException();

Instead of polling or waiting, the thread that initiates an asynchronous remote call can provide a completion callback that will be called by the RCF runtime, on a background thread, when the call completes:

typedef std::shared_ptr< RcfClient<I_PrintService> > PrintServicePtr;
void onCallCompleted(PrintServicePtr client, RCF::Future<int> fRet)
{
std::unique_ptr<RCF::Exception> ePtr = fRet.getAsyncException();
if (ePtr.get())
{
// Deal with any exception.
// ...
}
else
{
int charsPrinted = *fRet;
// ...
}
}
PrintServicePtr client( new RcfClient<I_PrintService>(RCF::TcpEndpoint(port)) );
auto onCompletion = [=]() { onCallCompleted(client, fRet); };
fRet = client->Print(
RCF::AsyncTwoway(onCompletion),
"Hello World");

Notice that the Future<> arguments are passed as arguments to the completion callback function. Future<> objects are internally reference counted, and can be copied freely, while still referring to the same underlying value.

An asynchronous call that is in progress, can be canceled by disconnecting the client:

client.getClientStub().disconnect();

If a RcfClient is destroyed while an asynchronous call is in progress, the connection is automatically disconnected and any asynchronous operations are canceled.

Asynchronous Dispatching

On the server-side, RCF will normally dispatch a remote call on the same server thread that reads the remote call request from the transport. Asynchronous dispatching allows you to instead transfer the remote call over to other threads, freeing up the RCF server thread to proceed with other remote calls.

The RCF::RemoteCallContext<> class is used to capture the server-side context of a remote call. RCF::RemoteCallContext<> objects can be copied into queues and stored for later execution on arbitrary application threads.

RCF::RemoteCallContext<> objects are created from within the corresponding servant implementation method. For comparison, here is a non-asynchronously dispatched Print() method:

RCF_BEGIN(I_PrintService, "I_PrintService")
// Returns number of characters printed.
RCF_METHOD_R1(int, Print, const std::string &)
RCF_END(I_PrintService)
class PrintService
{
public:
int Print(const std::string & s)
{
std::cout << "I_PrintService service: " << s << std::endl;
return (int) s.length();
}
};

To instead dispatch the call asynchronously, create a RCF::RemoteCallContext<> object in Print(), with template parameters corresponding to the method signature:

class PrintService
{
public:
int Print(const std::string & s)
{
// Capture current remote call context.
PrintContext printContext(RCF::getCurrentRcfSession());
// Start a new thread to dispatch the remote call.
std::thread printAsyncThread([=]() { printAsync(printContext); });
printAsyncThread.detach();
return 0;
}
void printAsync(PrintContext printContext)
{
const std::string & s = printContext.parameters().a1.get();
std::cout << "I_PrintService service: " << s << std::endl;
printContext.parameters().r.set( (int) s.length() );
printContext.commit();
}
};

Once created, the RCF::RemoteCallContext<> objects can be stored and copied like any other C++ object. When the Print() function returns, RCF will not send a response to the client. The response will only be sent when RCF::RemoteCallContext<>::commit() is called.

RCF::RemoteCallContext::parameters() provides access to all the parameters of the remote call, including the return value. The code sample above accesses an in-parameter:

const std::string & s = printContext.parameters().a1.get();

, and subsequently sets an out-parameter (in this case the return value):

printContext.parameters().r.set( (int) s.length() );