简体   繁体   English

如何从父级插槽中删除子级对象? 可能Boost :: asio特定

[英]How should I delete a child object from within a parent's slot? Possibly boost::asio specific

I have written a network server class that maintains a std::set of network clients. 我编写了一个网络服务器类,该类维护网络客户端的std :: set。 The network clients emit a signal to the network server on disconnect (via boost::bind). 网络客户端在断开连接时(通过boost :: bind)向网络服务器发出信号。 When a network client disconnects, the client instance needs to be removed from the Set and eventually deleted. 当网络客户端断开连接时,需要将客户端实例从集合中删除并最终删除。 I would think this is a common pattern, but I am having problems that might, or might not, be specific to ASIO. 我认为这是一种常见的模式,但是我遇到的问题可能是(也可能不是)ASIO特有的。

I've tried to trim down to just the relevant code: 我已经尝试将相关代码简化为:

/** NetworkServer.hpp **/
class NetworkServices : private boost::noncopyable
{
public:
    NetworkServices(void);
    ~NetworkServices(void);

private:
    void run();
    void onNetworkClientEvent(NetworkClientEvent&); 

private:
    std::set<boost::shared_ptr<const NetworkClient>> clients;
};


/** NetworkClient.cpp **/
void NetworkServices::run()
{
    running = true;

    boost::asio::io_service::work work(io_service); //keeps service running even if no operations

    // This creates just one thread for the boost::asio async network services
    boost::thread iot(boost::bind(&NetworkServices::run_io_service, this));

    while (running)
    {
        boost::system::error_code err;
        try
        {
            tcp::socket* socket = new tcp::socket(io_service);
            acceptor->accept(*socket, err);

            if (!err)
            {
                NetworkClient* networkClient = new NetworkClient(io_service, boost::shared_ptr<tcp::socket>(socket));
                networkClient->networkClientEventSignal.connect(boost::bind(&NetworkServices::onNetworkClientEvent, this, _1));

                clients.insert(boost::shared_ptr<NetworkClient>(networkClient));

                networkClient->init(); //kicks off 1st asynch_read call
            }
        }
        // etc...

    }
}

void NetworkServices::onNetworkClientEvent(NetworkClientEvent& evt)
{
    switch(evt.getType())
    {
        case NetworkClientEvent::CLIENT_ERROR :
        {
            boost::shared_ptr<const NetworkClient> clientPtr = evt.getClient().getSharedPtr();

            // ------ THIS IS THE MAGIC LINE -----
            // If I keep this, the io_service hangs. If I comment it out,
            // everything works fine (but I never delete the disconnected NetworkClient). 

            // If actually deleted the client here I might expect problems because it is the caller
            // of this method via boost::signal and bind.  However, The clientPtr is a shared ptr, and a
            // reference is being kept in the client itself while signaling, so
            // I would the object is not going to be deleted from the heap here. That seems to be the case.
            // Never-the-less, this line makes all the difference, most likely because it controls whether or not the NetworkClient ever gets deleted.
            clients.erase(clientPtr);

            //I should probably put this socket clean-up in NetworkClient destructor. Regardless by doing this,
            // I would expect the ASIO socket stuff to be adequately cleaned-up after this.

            tcp::socket& socket = clientPtr->getSocket();
            try {
                socket.shutdown(boost::asio::socket_base::shutdown_both);
                socket.close();
            }
            catch(...) {
                CommServerContext::error("Error while shutting down and closing socket.");
            }
            break;

        }
        default :
        {
            break;
        }
    }
}



/** NetworkClient.hpp **/
class NetworkClient : public boost::enable_shared_from_this<NetworkClient>, Client
{
    NetworkClient(boost::asio::io_service& io_service,
                  boost::shared_ptr<tcp::socket> socket);
    virtual ~NetworkClient(void);

    inline boost::shared_ptr<const NetworkClient> getSharedPtr() const
    {
        return shared_from_this();
    };

    boost::signal <void (NetworkClientEvent&)>    networkClientEventSignal;

    void onAsyncReadHeader(const boost::system::error_code& error,
                           size_t bytes_transferred);

};

/** NetworkClient.cpp - onAsyncReadHeader method called from io_service.run()
    thread as result of an async_read operation. Error condition usually 
    result of an unexpected client disconnect.**/
void NetworkClient::onAsyncReadHeader(  const boost::system::error_code& error,
                        size_t bytes_transferred)
{
    if (error)
    {

        //Make sure this instance doesn't get deleted from parent/slot deferencing
        //Alternatively, somehow schedule for future delete?
        boost::shared_ptr<const NetworkClient> clientPtr = getSharedPtr();

        //Signal to service that this client is disconnecting
        NetworkClientEvent evt(*this, NetworkClientEvent::CLIENT_ERROR);
        networkClientEventSignal(evt);

        networkClientEventSignal.disconnect_all_slots();

        return;
    }

I believe it's not safe to delete the client from within the slot handler because the function return would be ... undefined? 我认为从插槽处理程序中删除客户端是不安全的,因为函数返回将是...未定义的? (Interestingly, it doesn't seem to blow up on me though.) So I've used boost:shared_ptr along with shared_from_this to make sure the client doesn't get deleted until all slots have been signaled. (有趣的是,它似乎并没有让我失望。)因此,我使用了boost:shared_ptr和shared_from_this来确保客户端不会被删除,直到所有插槽都发出信号为止。 It doesn't seem to really matter though. 但这似乎并不重要。

I believe this question is not specific to ASIO, but the problem manifests in a peculiar way when using ASIO. 我相信这个问题不是ASIO特有的,但是在使用ASIO时,问题会以一种特殊的方式表现出来。 I have one thread executing io_service.run(). 我有一个线程执行io_service.run()。 All ASIO read/write operations are performed asynchronously. 所有ASIO读/写操作都是异步执行的。 Everything works fine with multiple clients connecting/disconnecting UNLESS I delete my client object from the Set per the code above. 多个客户端连接/断开时一切正常,除非我按照上述代码从Set中删除客户端对象。 If I delete my client object, the io_service seemingly deadlocks internally and no further asynchronous operations are performed unless I start another thread. 如果删除客户端对象,则io_service似乎在内部死锁,除非启动另一个线程,否则不会执行进一步的异步操作。 I have try/catches around the io_service.run() call and have not been able to detect any errors. 我在io_service.run()调用周围遇到了try / catching,但无法检测到任何错误。

Questions: 问题:

  1. Are there best practices for deleting child objects, that are also signal emitters, from within parent slots? 是否存在从父级插槽中删除也是信号发射器的子对象的最佳实践?

  2. Any ideas as to why the io_service is hanging when I delete my network client object? 关于删除网络客户端对象时io_service为什么挂起的任何想法?

you can store weak_ptr's in the set, so shared_ptr will be kept only by asio and released on disconnection automatically. 您可以将weak_ptr存储在集合中,因此shared_ptr仅由asio保留,并在断开连接时自动释放。 remove corresponding weak_ptr from set in ~Client() 从〜Client()中的设置中删除相应的weak_ptr

I've finally figured it out, and the short answer is that it was primarily a coding/threading mistake on my part. 我终于弄清楚了,简短的答案是,这主要是我的编码/线程错误。 I determined this by creating a simpler standalone code example, and found that it didn't exhibit the same behavior. 我通过创建一个更简单的独立代码示例来确定这一点,并发现它没有表现出相同的行为。 I should've done this in the first place and I'm sorry to have wasted anyone's time. 我本来应该这样做的,但很抱歉浪费任何人的时间。 To answer my original questions in full though: 要完整回答我的原始问题:

1 - Are there best practices for deleting child objects, that are also signal emitters, from within parent slots? 1-是否有从父级插槽中删除也是信号发射器的子对象的最佳实践?

No one has really answered this. 没有人真正回答过这个问题。 I think my suggestion and code example above where I use shared_from_this works fine. 我认为上面的建议和代码示例使用了shared_from_this,效果很好。

Also, as pointed out by villintehaspam in comments above, it might be better to use boost::signal2 which seemingly has better support for managing signal lifetime. 而且,正如villintehaspam在上面的评论中指出的那样,最好使用boost :: signal2,它似乎对管理信号寿命具有更好的支持。

2 - Any ideas as to why the io_service is hanging when I delete my network client object 2-关于为什么删除网络客户端对象时io_service挂起的任何想法

My mistake was that the destructor in my NetworkClient was triggering an operation that caused the current thread (and only thread available to handle asych IO operations) to block indefinitely. 我的错误是我的NetworkClient中的析构函数正在触发一个操作,该操作导致当前线程(并且仅可用于处理异步IO操作的线程)无限期阻塞。 I didn't realize that was happening. 我没有意识到那件事正在发生。 New clients were still able to connect because of how I handle the acceptor in it's own thread independent of the io_service asynch operations. 由于我如何在独立于io_service异步操作的自有线程中处理接受器,因此新客户端仍然能够连接。 Of course even though I scheduled asynch operations for the new client, they never fired because the one thread I made available to the io_service was still blocked. 当然,即使我为新客户端安排了异步操作,但它们从未触发,因为我对io_service可用的一个线程仍然被阻止。

Thanks to everyone that took the time to look at this. 感谢所有花时间研究此问题的人。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM