7

boost::asio::deadline_timer削除されたクラスに属する関数がによって呼び出され、セグメンテーション違反が発生することがある実際のコードに問題があります。

私が抱えている問題は、deadline_timerの削除が同じio_serviceの別のタイマーから実行されることです。最初の呼び出しを削除deadline_timerすると、実行する関数への最後の呼び出しが1回トリガーされ、boost::asio::error::operation_abortedエラーが発生します。io_serviceただし、これは削除が終了した後(同じ)にのみスケジュールできますが、それまでにオブジェクトはすでに削除されているため、無効になります。

だから私の質問は:どうすればこれが起こらないようにすることができますか?

以下は、同じ障害のある単純化された例です。

//============================================================================
// Name        : aTimeToKill.cpp
// Author      : Pelle
// Description : Delete an object using a timer, from a timer
//============================================================================

#include <iostream>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>

using namespace std;
using namespace boost;

struct TimeBomb
{
    bool m_active;
    asio::deadline_timer m_runTimer;

    TimeBomb(boost::asio::io_service& ioService)
    : m_active(true)
    , m_runTimer(ioService)
    {
        cout << "Bomb placed @"<< hex << (int)this << endl;
        m_runTimer.expires_from_now(boost::posix_time::millisec(1000));
        m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, this, _1));
    }

    ~TimeBomb()
    {
        m_active = false;
        m_runTimer.cancel();
        cout << "Bomb defused @"<< hex << (int)this << endl;
    }

    void executeStepFunction(const boost::system::error_code& error)
    {
        // Canceled timer
        if (error ==  boost::asio::error::operation_aborted)
        {
            std::cout << "Timer aborted: " << error.message() << " @" << std::hex << (int)this << std::endl;
            return;
        }
        if (m_active)
        {
            // Schedule next step
            cout << "tick .." <<endl;
            m_runTimer.expires_from_now(
                    boost::posix_time::millisec(1000));
            m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, this, _1));
        }
    }
};

struct BomberMan
{
    asio::deadline_timer m_selfDestructTimer;
    TimeBomb* myBomb;

    BomberMan(boost::asio::io_service& ioService)
    : m_selfDestructTimer(ioService)
    {
        cout << "BomberMan ready " << endl;
        myBomb = new TimeBomb(ioService);
        m_selfDestructTimer.expires_from_now(boost::posix_time::millisec(10500));
        m_selfDestructTimer.async_wait(boost::bind(&BomberMan::defuseBomb, this, _1));
    }

    void defuseBomb(const boost::system::error_code& error)
    {
        cout << "Defusing TimeBomb" << endl;
        delete myBomb;
    }
};


int main()
{
    boost::asio::io_service m_ioService;
    BomberMan* b = new BomberMan(m_ioService);
    m_ioService.run();
    return 0;
}


./aTimeToKill
BomberMan ready 
Bomb placed @9c27198
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
Defusing TimeBomb
Bomb defused @9c27198
Timer aborted: Operation canceled @9c27198

最後の行は削除後に印刷され、私の問題を示しています。

4

4 に答える 4

6

この問題を解決するための典型的なレシピは、shared_ptr

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>

#include <iostream>

using namespace std;

struct TimeBomb : public boost::enable_shared_from_this<TimeBomb>
{
    bool m_active;
    boost::asio::deadline_timer m_runTimer;

    TimeBomb(boost::asio::io_service& ioService)
    : m_active(true)
    , m_runTimer(ioService)
    {
        cout << "Bomb placed @"<< hex << this << endl;
        m_runTimer.expires_from_now(boost::posix_time::millisec(1000));
    }

    void start()
    {
        m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, shared_from_this(), _1));
    }

    void stop()
    {
        m_runTimer.cancel();
    }

    ~TimeBomb()
    {
        m_active = false;
        m_runTimer.cancel();
        cout << "Bomb defused @"<< hex << this << endl;
    }

    void executeStepFunction(const boost::system::error_code& error)
    {
        // Canceled timer
        if (error ==  boost::asio::error::operation_aborted)
        {
            std::cout << "Timer aborted: " << error.message() << " @" << std::hex << this << std::endl;
            return;
        }
        if (m_active)
        {
            // Schedule next step
            cout << "tick .." <<endl;
            m_runTimer.expires_from_now(
                    boost::posix_time::millisec(1000));
            m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, shared_from_this(), _1));
        }
    }
};

struct BomberMan
{
    boost::asio::deadline_timer m_selfDestructTimer;
    boost::shared_ptr<TimeBomb> myBomb;

    BomberMan(boost::asio::io_service& ioService)
    : m_selfDestructTimer(ioService)
    {
        cout << "BomberMan ready " << endl;
        myBomb.reset( new TimeBomb(ioService) );
        myBomb->start();
        m_selfDestructTimer.expires_from_now(boost::posix_time::millisec(10500));
        m_selfDestructTimer.async_wait(boost::bind(&BomberMan::defuseBomb, this, _1));
    }

    void defuseBomb(const boost::system::error_code& error)
    {
        cout << "Defusing TimeBomb" << endl;
        myBomb->stop();
    }
};


int main()
{
    boost::asio::io_service m_ioService;
    BomberMan* b = new BomberMan(m_ioService);
    m_ioService.run();
    return 0;
}
于 2012-10-25T14:44:18.920 に答える
2

boost::shared_ptrこれが、とがある理由ですboost::enable_shared_from_this。次のようにTimeBombクラスを継承します。boost::enable_shared_from_this

struct TimeBomb : public boost::enable_shared_from_this< TimeBomb >
{
...
}

ベア ptr の代わりに共有 ptr をインスタンス化します。

boost::shared_ptr< TimeBomb > myBomb;
...
myBomb.reset( new TimeBomb(ioService) );

そして最後に、ハンドラーを構築する代わりにTimeBomb使用されます。shared_from_this()this

m_runTimer.async_wait( boost::bind( &TimeBomb::executeStepFunction, shared_from_this(), _1));

そしてもちろん、TimeBombクラスはメソッドを公開する必要がcancelあります。これにより、shared_ptr を削除したり、この場合はリセットしたりするのではなく、非同期操作をキャンセルできます。

于 2012-10-25T14:38:55.333 に答える
0

本当に簡単な修正のために、これはどうですか?(変更が必要な部分のみを含めました)

タイマーキャンセル時にのみスタック変数にアクセスするため、機能します。もちろん、デストラクタでハンドラーをコールバックする必要はまったくありませんが、実際のコードでは何らかの理由でこれが必要であると想定しています。

~TimeBomb()
{
    m_active = false;
    executeStepFunction(boost::asio::error::interrupted);
    m_runTimer.cancel();
    cout << "Bomb defused @"<< hex << (int)this << endl;
}

void executeStepFunction(const boost::system::error_code& error)
{
    // Canceled timer
    if (error ==  boost::asio::error::operation_aborted)
    {
        return;
    }
    if (error ==  boost::asio::error::interrupted)
    {
        std::cout << "Timer aborted: " << error.message() << " @" << std::hex << (int)this << std::endl;
        return;
    }
...
于 2012-10-26T11:43:55.750 に答える
0

Sam Miller からの shared_ptr の回答は機能します。これは、shared_ptr を使用すると、BomberMan の存続期間中 TimeBomb がぶらぶらし続けるためです。これはあなたにとって大丈夫かもしれませんし、そうでないかもしれません。

より完全な解決策の提案は、TimeBomb インスタンスをファクトリから取得し、明示的に新規作成して削除するのではなく、終了時にリリースして戻すことです (所有していないため、shared_ptrs ではなく標準ポインターとして保持します)。ライフサイクルを制御している場合でも)。工場は、キャンセルされるまでそれらをぶら下げておき、その後削除することができます. Sam Miller の stop() 関数をそのまま使用します。

これを実装するには、次の行に沿ってインターフェイスからファクトリを派生させます。

class ITimeBombObserver 
{
public:
    virtual void AllOperationsComplete(TimeBomb& TmBmb)=0;
};

ファクトリを構築時に ITimeBombObserver として各 TimeBomb に渡し、TimeBomb のキャンセルでこの関数を呼び出すようにします。ファクトリは、「使用済み」の TimeBomb が作成またはリリースされるたびにクリーンアップするか、スケジュールされたクリーンアップを使用するか、アプリケーションに最も適していると思われるその他の方法を使用してクリーンアップできます。

このメソッドを使用することで、BomberMan は defuseBomb() で TimeBomb を明示的に解放する必要さえありません。stop() の呼び出しで自動的に解放できます (ただし、この場合、ポインターが効果的になるために null にする必要があります)。この時点では使用できません)。これが良いアイデアであるかどうかは、実際の問題に依存するため、判断はあなたに任せます。

于 2012-10-25T15:18:09.823 に答える