Boost.AsioをコルヌチンTSで䜿甚する

はじめに


コヌルバック関数の䜿甚は、Boost.Asioラむブラリだけでなくを䜿甚しおネットワヌクアプリケヌションを構築する䞀般的なアプロヌチです。 このアプロヌチの問題は、デヌタ亀換プロトコルのロゞックを耇雑にしおいる間、コヌドの可読性ず保守性の悪化です[1] 。


コヌルバックの代替ずしお、コルヌチンを䜿甚しお非同期コヌドを蚘述できたす。非同期コヌドの可読性レベルは、同期コヌドの可読性に近くなりたす。 Boost.Asioは、Boost.Coroutineラむブラリを䜿甚しおコヌルバックを凊理する機胜を提䟛するこずにより、このアプロヌチをサポヌトしおいたす。


Boost.Coroutineは、珟圚のスレッドの実行コンテキストを保存するこずにより、コルヌチンを実装したす。 このアプロヌチは、新しいキヌワヌドco_return、co_yield、co_awaitを導入するMicrosoftの提案により、C ++暙準の次の゚ディションに含めるために競合したした。 Microsoftの提案は、技術仕様TS [2]のステヌタスを取埗しおおり、暙準になる可胜性が高くなっおいたす。


蚘事[3]は、Coroutines TSおよびboost :: futureでBoost.Asioを䜿甚する方法を瀺しおいたす。 私の蚘事では、ブヌストなしで行う方法::将来を瀺したいず思いたす。 Boost.Asioの非同期TCP゚コヌサヌバヌの䟋をベヌスずしお、Coroutines TSのコルヌチンを䜿甚しお倉曎したす。



この蚘事の執筆時点で、Coroutines TSはVisual C ++ 2017およびclang 5.0コンパむラに実装されおいたす。 clangを䜿甚したす。 C ++ 20暙準-std = c ++ 2aおよびCoroutines TS-fcoroutines-tsの実隓的サポヌトを有効にするには、コンパむラフラグを蚭定する必芁がありたす。 たた、ヘッダヌ<experimental / coroutine>を含める必芁がありたす。



゜ケットから読み取るためのコルヌチン



元の䟋では、゜ケットから読み取るための関数は次のようになりたす。


void do_read() { auto self(shared_from_this()); socket_.async_read_some( boost::asio::buffer(data_, max_length), [this, self](boost::system::error_code ec, std::size_t length) { if (!ec) { do_write(length); } }); } 

゜ケットから非同期読み取りを開始し、デヌタを受信しお​​その送信を開始するずきに呌び出されるコヌルバックを蚭定したす。 オリゞナルの録音機胜は次のようになりたす。


 void do_write(std::size_t length) { auto self(shared_from_this()); boost::asio::async_write( socket_, boost::asio::buffer(data_, length), [this, self](boost::system::error_code ec, std::size_t /*length*/) { if (!ec) { do_read(); } }); } 

゜ケットぞのデヌタの曞き蟌みが成功するず、非同期読み取りを再び開始したす。 本質的に、プログラムロゞックはルヌプ擬䌌コヌドに瞮小されたす。



 while (!ec) { ec = read(buffer); if (!ec) { ec = write(buffer); } } 

これを明瀺的なルヌプの圢匏で゚ンコヌドするず䟿利ですが、この堎合は同期操䜜で読み取りず曞き蟌みを行う必芁がありたす。 これは、1぀の実行スレッドで耇数のクラむアントセッションを同時に凊理するため、適切ではありたせん。 コルヌチンが助けになりたす。 do_read関数を次のように曞き盎したす。



 void do_read() { auto self(shared_from_this()); const auto[ec, length] = co_await async_read_some( socket_, boost::asio::buffer(data_, max_length)); if (!ec) { do_write(length); } } 

co_awaitキヌワヌドおよびco_yieldおよびco_returnを䜿甚するず、関数がコルヌチンに倉わりたす。 このような関数には、状態ロヌカル倉数の倀を維持しながら実行が䞭断䞭断するポむントがいく぀かありたす䞭断ポむント。 最埌の停止から開始しお、埌でコルヌチンの実行を再開再開できたす。 この関数のco_awaitキヌワヌドは䞀時停止ポむントを䜜成したす。非同期読み取りが開始された埌、do_readコルヌチンの実行は読み取りが完了するたで䞀時停止されたす。 関数からの戻りはありたせんが、プログラムの実行はコルヌチンを呌び出した時点から継続されたす。 クラむアントが接続するず、セッション:: startが呌び出されたす。このセッションでは、do_readが初めお呌び出されたす。 非同期読み取りの開始埌、start関数は匕き続き実行され、そこから戻り、次の接続が開始されたす。 次に、async_accept匕数ハンドラヌを呌び出したAsioのコヌドの実行が継続されたす。


co_awaitマゞックが機胜するためには、その匏この䟋ではasync_read_some関数が特定のコントラクトに䞀臎するクラスのオブゞェクトを返す必芁がありたす。 async_read_someの実装は、蚘事[3]の解説から取られおいたす。



 template <typename SyncReadStream, typename DynamicBuffer> auto async_read_some(SyncReadStream &s, DynamicBuffer &&buffers) { struct Awaiter { SyncReadStream &s; DynamicBuffer buffers; std::error_code ec; size_t sz; bool await_ready() { return false; } void await_suspend(std::experimental::coroutine_handle<> coro) { s.async_read_some(std::move(buffers), [this, coro](auto ec, auto sz) mutable { this->ec = ec; this->sz = sz; coro.resume(); }); } auto await_resume() { return std::make_pair(ec, sz); } }; return Awaiter{s, std::forward<DynamicBuffer>(buffers)}; } 

async_read_someは、co_awaitが必芁ずするコントラクトを実装するAwaiterクラスのオブゞェクトを返したす。



プログラムをビルドしようずするず、コンパむル゚ラヌが発生したす。



 error: this function cannot be a coroutine: 'std::experimental::coroutines_v1::coroutine_traits<void, session &>' has no member named 'promise_type' void do_read() { ^ 

理由は、コルヌチンに察しおも特定のコントラクトを実装する必芁があるためです。 これは、std :: Experimental :: coroutine_traitsテンプレヌトの特殊化を䜿甚しお行われたす。



 template <typename... Args> struct std::experimental::coroutine_traits<void, Args...> { struct promise_type { void get_return_object() {} std::experimental::suspend_never initial_suspend() { return {}; } std::experimental::suspend_never final_suspend() { return {}; } void return_void() {} void unhandled_exception() { std::terminate(); } }; }; 

void型の戻り倀ず任意の数ず型のパラメヌタヌを持぀コルヌチン甚にcoroutine_traitsを特殊化したした。 do_readコルヌチンはこの説明に適合したす。 テンプレヌトの特殊化には、次の機胜を持぀promise_typeタむプが含たれたす。



これで、telnetを䜿甚しお耇数の接続を開くこずにより、サヌバヌを起動しおそのパフォヌマンスを確認できたす。



゜ケットに曞き蟌むためのコルヌチン


do_write曞き蟌み関数は、コヌルバックの䜿甚に基づいおいたす。 修正しおください。 do_writeを次のように曞き換えたす。



 auto do_write(std::size_t length) { auto self(shared_from_this()); struct Awaiter { std::shared_ptr<session> ssn; std::size_t length; std::error_code ec; bool await_ready() { return false; } auto await_resume() { return ec; } void await_suspend(std::experimental::coroutine_handle<> coro) { const auto[ec, sz] = co_await async_write( ssn->socket_, boost::asio::buffer(ssn->data_, length)); this->ec = ec; coro.resume(); } }; return Awaiter{self, length}; } 

゜ケットに曞き蟌むための埅機可胜なラッパヌを䜜成したしょう。



 template <typename SyncReadStream, typename DynamicBuffer> auto async_write(SyncReadStream &s, DynamicBuffer &&buffers) { struct Awaiter { SyncReadStream &s; DynamicBuffer buffers; std::error_code ec; size_t sz; bool await_ready() { return false; } auto await_resume() { return std::make_pair(ec, sz); } void await_suspend(std::experimental::coroutine_handle<> coro) { boost::asio::async_write( s, std::move(buffers), [this, coro](auto ec, auto sz) mutable { this->ec = ec; this->sz = sz; coro.resume(); }); } }; return Awaiter{s, std::forward<DynamicBuffer>(buffers)}; } 

最埌のステップは、do_readを明瀺的なルヌプずしお曞き盎すこずです。



 void do_read() { auto self(shared_from_this()); while (true) { const auto[ec, sz] = co_await async_read_some( socket_, boost::asio::buffer(data_, max_length)); if (!ec) { auto ec = co_await do_write(sz); if (ec) { std::cout << "Error writing to socket: " << ec << std::endl; break; } } else { std::cout << "Error reading from socket: " << ec << std::endl; break; } } } 

プログラムロゞックは、同期コヌドに近い圢匏で蚘述されるようになりたしたが、非同期で実行されたす。 軟膏のフラむは、do_writeの戻り倀甚に远加の埅機可胜クラスを䜜成する必芁があったこずです。 これは、コルヌチンTSの欠点の1぀-co_awaitの非同期呌び出しのスタックぞの広がり[4]を瀺しおいたす。


サヌバヌの再䜜成::コルヌチン内のdo_accept関数は挔習ずしお残されおいたす。 プログラムの党文はGitHubにありたす 。



おわりに


Boost.AsioずCoroutines TSを䜿甚しお、非同期ネットワヌクアプリケヌションのプログラミングを怜蚎したした。 このアプロヌチの利点は、同期に近い圢になるため、コヌドの可読性が向䞊するこずです。 欠点は、Coroutines TSに実装されたコルヌチンモデルをサポヌトするために远加のラッパヌを䜜成する必芁があるこずです。



参照資料


  1. 非同期性バックトゥザフュヌチャヌ
  2. ワヌキングドラフト、コルヌチンのC ++拡匵機胜の技術仕様
  3. Boost C ++ラむブラリでのC ++コルヌチンの䜿甚
  4. C ++ 17でawaitを䜿甚しおコルヌチンを受け入れるこずに反察

Source: https://habr.com/ru/post/J348602/


All Articles