template<typename... Types>
class AlgAudio::LateReturn< Types >
The LateReturn class template provides a global, universal mechanism for managing asynchronous code execution. The idea is that some functions may not be able to return their value immediatelly, but only after some longer time. Usually, such situation might be resolved by allowing the function to block execution, and simply call the function in another thread. Instead, a function can return a LateReturn<X>, which represents a value that is not yet available. The called can then use that returned LateReturn instance to define what action should be taken when that value arrives. A simple example of usage might be:
LateReturn<std::string> GetServerVersion();
...
void PrintServerInfo(){
GetServerVersion().Then([](std::string version){
std::cout << "Sever version is " << version << std::endl;
});
std::cout << "Requested version info from server, will report when it arrives..." << std::endl;
}
...
In the presented example, the code within lambda function is likely to be executed when PrintServerInfo() has already returned. However, if GetServerVersion has cached data, it may have the resulting value ready immediatelly, and in such case the lambda function will be called before the other text message is printed. The order in which continuation functions (the functions set with Then(), which represent action which should be called when the returned value is ready) are called may appear random and confusing, but the exact order should never be significant. What is guaranteed is that each continuation function is called as soon as both the latereturned value and the continuation function are set.
The lifetime of the stored function is kept to minumum. This is especially important when using lambdas to define continuations:
void function(){
Object x;
LateReturningFunc().Then([&x](){
x.value = 5;
});
}
In the above example, setting x.value may be an invalid operation, because the x Object may have been already destructed when the LateReturningFunc completes it's work.
One possible solution is to use shared pointers to prolong object lifetime as long as the stored function is needed. The stored function will be destructed just after it is called, and so will be the captured shared_ptr. However, it is possible to create ownership cycles, if such a shared pointer gets stored in a LateReturn that is owned by the same object.
Similarly, there is nothing wrong in capturing this by a lambda that is passed to Then(), just keep in mind what the LateReturn lifetime will be, and whether it is guaranteed that this will still exist by the time there continuation function is invoked.
The return value may never become ready, for example if the latereturning function keeps waiting for en event that never happens. In such case the continuation will never get called.
It is also possible that when LateReturning function has failed and cannot provide a value, it decides to thow an exception, to notify whoever called it about some kind of a problem. Obviously, using a try{}catch(){} block won't work, because at the time an exception occurs, the actual stack for the call to latereturning function no longer exists. Instead, you can use Catch() to set a handler for a given exception. Example:
GetServerVersion().Then([](std::string version){
std::cout << version << std::endl;
}).Catch<Exceptions::Communication>([](std::shared_ptr<Exceptions::Exception> ex){
std::cout << "Failed to get server version, reason: " << ex->reason << std::endl;
});
Similarly to continuations, exception handling functions are also called when appropriate, which may mean a long delay.
See Relay documentation for details on how to write a LateReturning function.
- See also
- Relay