I have some asio code which is used for transferring a file from a client. This code is working correctly and is as follows:
bool FileTransferBehaviour::ConnectAndTransferFile(const std::string& _sender,
const std::string& _filePath,
size_t _numBytes)
{
// use a coroutine to accept the connection and receive the file.
bool result = false;
asio::co_spawn(m_ioContext,
[&result, &filePath, _numBytes, this]() mutable
-> asio::awaitable<void> {
auto maybeSock = co_await CoAwaitClientConnection();
if (maybeSock.has_value()) {
result = ReceiveFile(maybeSock.value(), filePath, _numBytes);
}
}, asio::detached);
m_ioContext.restart();
m_ioContext.run();
return result;
}
Currently a result
is set in the internal lambda expression. I would prefer to return this result from the coroutine instead of capturing a local variable by reference, i.e.
bool FileTransferBehaviour::ConnectAndTransferFile(const std::string& _sender,
const std::string& _filePath,
size_t _numBytes)
{
// use a coroutine to accept the connection and receive the file.
bool result = co_await asio::co_spawn(m_ioContext,
&filePath, _numBytes, this]() mutable
-> asio::awaitable<bool> {
auto maybeSock = co_await CoAwaitClientConnection();
if (maybeSock.has_value()) {
co_return ReceiveFile(maybeSock.value(), filePath, _numBytes);
}
co_return false;
}, asio::detached);
m_ioContext.restart();
m_ioContext.run();
return result;
}
When I change the return type of the lambda the code fails with
FileTransferBehaviour.cpp(101,42): error C2228: left of '.await_ready' must have
class/struct/union [MyProject,vcxproj]
FileTransferBehaviour.cpp(101,17): error C2440: 'initializing': cannot convert from
'void' to 'bool' [MyProject.vcxproj]
I am using C++20 with CL version 19.37.32824 (Visual Studio 2022)
With GCC 11.4.0 I get the following error:
FileTransferBehaviour.cpp:101:19: error: unable to find the promise type for this coroutine
101 | bool result = co_await asio::co_spawn(m_ioContext,
What am I missing?
2
Answers
In your first version,
ConnectAndTransferFile
is not a coroutine. It is a function that spawns a coroutine that happens to be defined as a lambda within the function. This coroutine is handed off to ASIO, and your outer non-coroutine essentially waits for it to finish before returning.This is a synchronous operation.
In your second version, you have changed
ConnectAndTransferFile
to become a coroutine. That’s what happened when you didco_await asio::co_spawn(...)
directly inConnectAndTransferFile
.Of course, if a function is a coroutine, then it cannot
return
something; it mustco_return
it. Also, the function’s signature now needs to indicate the promise type to be used with that coroutine. This is typically done using the return value. In your case, you probably mean for it to returnasio::awaitable<bool>
.But this means that the code that invoked the coroutine has to wait on it to extract the
bool
result. Your coroutine probably also should notrestart
andrun
the context.I’d suggest running the io service on some other thread. That way you can post a promise and await the future:
With a working live example:
Live On Coliru
Tested with
Which prints
Notes
It seems easier to just set the promise in a void awaitable:
In fact, the "bool" and "optional" return values seem antipattern here to deal with exceptional situations. I’d simplify all the way:
Again with a Live Demo, this time also demonstrating error handling:
Live On Coliru
Which prints: