Comments (8)
Hi,
the comment in the code is in fact accurate. Asio describes the thread-switching behavior in their documentation. Although I find it rather difficult to understand, let me try to explain it in my own words.
Take the following example:
asio::io_context io_context1;
asio::steady_timer timer{io_context1};
asio::io_context io_context2;
asio::co_spawn(
io_context2,
[&]() -> asio::awaitable<void>
{
// on io_context2 thread
// Timer is handled on io_context1 thread
// But after completion it will:
// `asio::dispatch(asio::get_associated_executor(use_awaitable_completion_handler,
// timer.get_executor()), use_awaitable_completion_handler)`
// which causes the switch back to io_context2 thread
co_await timer.async_wait(asio::use_awaitable);
// on io_context2 thread
},
asio::detached);
auto g = asio::make_work_guard(io_context1);
std::thread t{[&]
{
io_context1.run();
}};
io_context2.run();
g.reset();
t.join();
Asio performs the following steps on this line co_await timer.async_wait(asio::use_awaitable);
:
- Convert the completion token (
asio::use_awaitable
) into a completion handler usingasio::async_result
machinery. The details do not matter much, but the result is a callable object (called completion handler) that is to be invoked when the timer completes. - Get the I/O executor by calling
timer.get_executor()
and use that to do whatever is necessary to initiate and wait for the timer (system calls, epoll, etc.) - Meanwhile create the completion handler executor by calling
asio::get_associated_executor(completion_handler, timer.get_executor())
and establish work-tracking (callasio::make_work_guard()
) until the timer completes.use_awaitable
's completion handler actually has an associated executor, it is the one used in the call toco_spawn
(io_context2). - When the timer completes, invoke the completion handler as if by calling
asio::dispatch(completion_handler_executor, completion_handler)
. This is where the thread switching is happening, going fromtimer.get_executor()
(io_context1) to the completion handler executor (io_context2). - The completion handler will then resume the coroutine.
Since asio-grpc tries to mimic Asio's behavior as much as possible, replacing asio::steady_timer
with agrpc::ServerRPC
and io_context with GrpcContext yields the same thread-switching result.
I hope that helps, let me know if something is still unclear.
from asio-grpc.
Correct, the co_spawn
call is actually hidden inside agrpc::register_awaitable_rpc_handler
. It actually invokes co_spawn like so:
asio::co_spawn(asio::get_associated_executor(completion_handler, first_argument_passed_to_register_awaitable_rpc_handler())
Asio internally always works with executors. There is a co_spawn
overload that takes an execution context, like GrpcContext, and essentially forwards the call to co_spawn(grpc_context.get_executor(), ...)
.
from asio-grpc.
Hi,
Thank you for the clear explanation. I tested it and everything worked just as you described. Your guidance made the thread-switching behavior in Asio much clearer.
Also, I want to say that asio-grpc is an amazing project. Really appreciate all the work you've put into it.
Best
from asio-grpc.
Thanks, glad I could help.
Btw, you can disable the thread-switching this way:
inline void use_inline_awaitable() { return asio::bind_executor(asio::system_executor(), asio::use_awaitable); }
co_await timer.async_wait(use_inline_awaitable());
This will cause async_wait
to resume the coroutine on whatever thread the timer's I/O executor is running on.
from asio-grpc.
Hi again,
I revisited your example and noticed that after co_await timer.async_wait(), the execution returns to the io_context2 thread, the same thread as before the co_await. So, in the writer code, before co_await rpc.write(), it's in the thread_pool, right? By the same logic, does this mean that after co_await rpc.write(), the execution also returns to the thread_pool?
from asio-grpc.
It doesn't matter what thread it was on before the co_await, what matters is the first argument passed to asio::co_spawn
. By default any co_await ... asio::use_awaitable
asynchronous operation started within the co_spawned coroutine will switch back to that first argument (provided the asynchronous operation follows Asio's completion handler executor rules explained earlier). I know this can be a bit unfortunate because the co_spawn call can be far away in some other source file.
from asio-grpc.
Great, it's finally clear now. Thank you so much.
Have a good day!
from asio-grpc.
writer
is (just a little far away) from the co_spawn that first argument is grpc_context or grpc_context's executor, right?
from asio-grpc.
Related Issues (20)
- Example with small changes crashes HOT 6
- Why I get const correctness problems HOT 16
- Generic server example without manual buffer serializaiton? HOT 4
- Using asynchronous gRPC server and client on same context HOT 9
- Asio-gRPC seems to have TSAN warnings HOT 8
- How to detect client closed connection HOT 6
- Can I call ServerBuilder::BuildAndStart() after GrpcContext::run() HOT 2
- Questions on how to switch from an GrpcContext to io_context and back HOT 6
- How to shutdown grpc clients HOT 1
- assertion failed: !started_ HOT 2
- Can I use asio-grpc inside an existing boost::asio application? HOT 7
- The agrpc::GrpcContext hangs forever HOT 13
- an upgrade from 2.5.1 to 2.9.1 leads to build errors HOT 6
- build fails with latest boost 1.84 HOT 4
- [Question]: Slowly receiveing client in long-lived streaming HOT 1
- InvokeHandler conflicts with Objective-C/C++ defines HOT 1
- Need some basic help! HOT 9
- How to make grPC Client to Establish connection based on IP address? HOT 2
- Update conan package to 3.0.0 HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from asio-grpc.