私はboost :: asioでリソース管理に頭を悩ませようとしています。対応するソケットがすでに破棄された後に呼び出されたコールバックが表示されます。この良い例は、boost :: asioの公式例です: http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/example/cpp11/chat/chat_client.cpp
私は特にcloseメソッドに関心があります。
void close()
{
io_service_.post([this]() { socket_.close(); });
}
この関数を呼び出した後、socket_を保持するchat_clientインスタンスを破棄すると、closeメソッドが呼び出される前にsocket_が破棄されます。また、保留中のasync_ *コールバックは、chat_clientが破棄された後に呼び出すことができます。
これをどのように正しく処理しますか?
socket_.close();
はほぼいつでも実行できますが、次の点に注意する必要があります。
close
を実行するときは常に、io_serviceがすでにキューに入れられたハンドラーを持つことができることに注意してください。そして、それらはとにかく古い状態/エラーコードで呼び出されます。close
は例外をスローできます。close
にはip :: tcp :: socketの破棄は含まれません。システムソケットを閉じるだけです。Connection
またはsocket
オブジェクトでenable_shared_from_thisを使用して行われます。socket.close()
を呼び出しても、ソケットは破棄されません。ただし、アプリケーションは、操作ハンドラーと完了ハンドラーが依存するオブジェクトの存続期間を管理する必要がある場合がありますが、これは必ずしもソケットオブジェクト自体である必要はありません。たとえば、バッファとソケットを保持し、client::handle_read()
の完了ハンドラーを持つ単一の未処理の読み取り操作を持つclient
クラスについて考えてみます。 close()
してソケットを明示的に破棄することはできますが、少なくともハンドラーが呼び出されるまで、バッファーとclient
インスタンスは有効なままである必要があります。
class client
{
...
void read()
{
// Post handler that will start a read operation.
io_service_.post([this]() {
async_read(*socket, boost::asio::buffer(buffer_);
boost::bind(&client::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
});
}
void handle_read(
const boost::system::error_code& error,
std::size_t bytes_transferred
)
{
// make use of data members...if socket_ is not used, then it
// is safe for socket to have already been destroyed.
}
void close()
{
io_service_.post([this]() {
socket_->close();
// As long as outstanding completion handlers do not
// invoke operations on socket_, then socket_ can be
// destroyed.
socket_.release(nullptr);
});
}
private:
boost::asio::io_service& io_service_;
// Not a typical pattern, but used to exemplify that outstanding
// operations on `socket_` are not explicitly dependent on the
// lifetime of `socket_`.
std::unique_ptr<boost::asio::socket> socket_;
std::array<char, 512> buffer_;
...
}
アプリケーションは、操作とハンドラーが依存するオブジェクトの存続期間を管理する責任があります。 チャットクライアントの例 これは、chat_client
インスタンスが破棄されることを保証することによって実現されますafterio_service.run()
を待つことにより、使用されなくなります。スレッド内に戻るにはjoin()
:
int main(...)
{
try
{
...
boost::asio::io_service io_service;
chat_client c(...);
std::thread t([&io_service](){ io_service.run(); });
...
c.close();
t.join(); // Wait for `io_service.run` to return, guaranteeing
// that `chat_client` is no longer in use.
} // The `chat_client` instance is destroyed.
catch (std::exception& e)
{
...
}
}
オブジェクトの存続期間を管理する一般的なイディオムの1つは、I/Oオブジェクトを enable_shared_from_this<>
から継承する単一のクラスで管理することです。クラスがenable_shared_from_this
から継承する場合、this
を管理する有効なshared_ptr
インスタンスを返すshared_from_this()
メンバー関数を提供します。 shared_ptr
のコピーは、ラムダのキャプチャリストなどの完了ハンドラーに渡されるか、インスタンスハンドルとしてbind()
に渡され、I/Oオブジェクトの有効期間がに延長されます。少なくともハンドラーと同じくらい。このアプローチの使用例については、Boost.Asio asynchronous TCP daytime server チュートリアルを参照してください。