drogonframework / drogon

Drogon: A C++14/17/20 based HTTP web application framework running on Linux/macOS/Unix/Windows
MIT License
11.57k stars 1.12k forks source link

websocket 如何支持协程 #1017

Closed wangganglab closed 6 months ago

marty1885 commented 3 years ago

Hi, WS clients and controllers does not support coroutines natively now. However, you can wrap a AsyncTask within them.

wsPtr->setMessageHandler([](const std::string &message,
                                const WebSocketClientPtr client&,
                                const WebSocketMessageType &type) {
                                [message, client, type]() -> AsyncTask {
                                    co_await you_function(...);
                                }();
}

Same works for servers

nqf commented 3 years ago

@marty1885 It seems that variables cannot be captured using =, Because it will be destructed, Do you know why?

int main() {
    {
        auto ptr = std::make_shared<std::string>("hh");
        drogon::async_run([ptr]() -> drogon::AsyncTask {
            std::cout << "start co_await test2()" << std::endl;
            try {
                std::cout << ptr.use_count() << std::endl;
                co_await test2();
                std::cout << ptr.use_count() << std::endl;
            } catch (...) {
                std::cout << "异常" << std::endl;
            }
            std::cout << "end co_await test2()" << std::endl;
            co_return;
        });
    }
}
➜ git:(master) ✗ ./a.out
start co_await test2()
2
test2 start sleep
test2 end sleep
0
end co_await test2()
marty1885 commented 3 years ago

@nqf May you share the full source code and the compiler you use? I can't tell without knowing that test2 is.

My guess is that you want to use sync_wait instead of async_run. async_runc returns immediately when it needs to wait, which in your case reaches the end of the program. Thus the true execution order is:

  1. main()
  2. async_run()
  3. Your lambda coroutine
  4. std::cout << "start co_await test2()" << std::endl;
  5. std::cout << ptr.use_count() << std::endl;
  6. test2()
  7. test2 suspends the coroutine
  8. Continue executing after async_run
  9. Reached program end. Destructing everything
  10. test2 got resumed while destructing
  11. std::cout << ptr.use_count() << std::endl
  12. std::cout << "end co_await test2()" << std::endl;
  13. Exit

Or put app().run() at the end of main to keep the event loop running.

Important: sync_wait accepts a Task<T> instead of a coroutine function. The syntax is rogon::async_run([ptr]() -> drogon::Task<> {...} ());


The naming is weird. And in most cases you want to use Task insead of AsyncTask. Task<> is fully async. And AsyncTask should be named FireAndForget, it's named that way because of internal C++ details.