web-dev-qa-db-ja.com

Boostasio期限タイマーを安全にキャンセルする

_boost::asio::basic_waitable_timer<std::chrono::steady_clock>_を安全にキャンセルしようとしています。

これによると answer 、このコードはその仕事をするはずです:

_timer.get_io_service().post([&]{timer.cancel();})
_

うまくいかないのではないかと思います。
私は何か間違ったことをしていますか?
これは私のコードです:

_#include <iostream>
#include "boost/asio.hpp"
#include <chrono>
#include <thread>
#include <random>

boost::asio::io_service io_service;
boost::asio::basic_waitable_timer<std::chrono::steady_clock> timer(io_service);
std::atomic<bool> started;

void handle_timeout(const boost::system::error_code& ec)
{
    if (!ec) {
        started = true;
        std::cerr << "tid: " << std::this_thread::get_id() << ", handle_timeout\n";
        timer.expires_from_now(std::chrono::milliseconds(10));
        timer.async_wait(&handle_timeout);
    } else if (ec == boost::asio::error::operation_aborted) {
        std::cerr << "tid: " << std::this_thread::get_id() << ", handle_timeout aborted\n";
    } else {
        std::cerr << "tid: " << std::this_thread::get_id() << ", handle_timeout another error\n";
    }
}

int main() {

    std::cout << "tid: " << std::this_thread::get_id() << ", Hello, World!" << std::endl;
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(1, 100);

    for (auto i = 0; i < 1000; i++) {

        started = false;
        std::thread t([&](){

            timer.expires_from_now(std::chrono::milliseconds(0));
            timer.async_wait(&handle_timeout);

            io_service.run();
        });

        while (!started) {};
        auto sleep = dis(gen);
        std::cout << "tid: " << std::this_thread::get_id() << ", i: " << i << ", sleeps for " << sleep << " [ms]" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(sleep));
        timer.get_io_service().post([](){
            std::cerr << "tid: " << std::this_thread::get_id() << ", cancelling in post\n";
            timer.cancel();
        });
//      timer.cancel();
        std::cout << "tid: " << std::this_thread::get_id() << ", i: " << i << ", waiting for thread to join()" << std::endl;
        t.join();
        io_service.reset();
    }

    return 0;
}
_

これは出力です:

.。
tid:140737335076608、handle_timeout
tid:140737335076608、handle_timeout
tid:140737353967488、i:2、スレッドがjoin()に参加するのを待っています
tid:140737335076608、投稿でキャンセル
tid:140737335076608、handle_timeoutは中止されました
tid:140737353967488、i:3、21 [ms]スリープします
tid:140737335076608、handle_timeout
tid:140737353967488、i:3、スレッドがjoin()に参加するのを待っています
tid:140737335076608、handle_timeout
tid:140737335076608、投稿でキャンセル
tid:140737335076608、handle_timeout
tid:140737335076608、handle_timeout
tid:140737335076608、handle_timeout
tid:140737335076608、handle_timeout
tid:140737335076608、handle_timeout
.。
永遠に続く...

ご覧のとおり、timer.cancel()は適切なスレッドから呼び出されています。

tid:140737335076608、投稿でキャンセル

しかし、ありません

tid:140737335076608、handle_timeoutは中止されました

その後。

メインは永遠に待ちます。

8
hudac

キャンセル安全です。

堅牢ではありません。タイマーが保留されていなかった場合、あなたはそのケースを説明しませんでした。その後、一度キャンセルしますが、完了ハンドラーが呼び出されると、新しい非同期待機が開始されます。

以下は、問題を追跡する方法に関する詳細な手順です。

[〜#〜]要約[〜#〜]TL; DR

時間のキャンセルは、飛行中の非同期操作のみをキャンセルします。

非同期呼び出しチェーンをシャットダウンする場合は、そのために追加のロジックを使用する必要があります。以下に例を示します。

ハンドラー追跡

で有効にする

_#define BOOST_ASIO_ENABLE_HANDLER_TRACKING 1
_

これにより、_boost/libs/asio/tools/handlerviz.pl_で視覚化できる出力が生成されます。

成功したトレース

enter image description here

ご覧のとおり、キャンセルが発生すると、_async_wait_は飛行中です。

「悪い」トレース

(無限に実行されるため切り捨てられました)

enter image description here

完了ハンドラーが_cc=system:0_(_cc=system:125_の場合)ではなく_operation_aborted_をどのように認識するかに注意してください。これは、投稿されたキャンセルが実際には「実行」されなかったという事実の症状です。唯一の論理的な説明(図には表示されていません)は、キャンセルが呼び出される前にタイマーがすでに期限切れになっていることです。

生のトレースを比較してみましょう¹

enter image description here

¹ノイズの多い違いを取り除く

それを検出する

だから、私たちはリードを持っています。検出できますか?

_    timer.get_io_service().post([](){
        std::cerr << "tid: " << std::this_thread::get_id() << ", cancelling in post\n";
        if (timer.expires_from_now() >= std::chrono::steady_clock::duration(0)) {
            timer.cancel();
        } else {
            std::cout << "PANIC\n";
            timer.cancel();
        }
    });
_

プリント:

_tid: 140113177143232, i: 0, waiting for thread to join()
tid: 140113177143232, i: 1, waiting for thread to join()
tid: 140113177143232, i: 2, waiting for thread to join()
tid: 140113177143232, i: 3, waiting for thread to join()
tid: 140113177143232, i: 4, waiting for thread to join()
tid: 140113177143232, i: 5, waiting for thread to join()
tid: 140113177143232, i: 6, waiting for thread to join()
tid: 140113177143232, i: 7, waiting for thread to join()
tid: 140113177143232, i: 8, waiting for thread to join()
tid: 140113177143232, i: 9, waiting for thread to join()
tid: 140113177143232, i: 10, waiting for thread to join()
tid: 140113177143232, i: 11, waiting for thread to join()
tid: 140113177143232, i: 12, waiting for thread to join()
tid: 140113177143232, i: 13, waiting for thread to join()
tid: 140113177143232, i: 14, waiting for thread to join()
tid: 140113177143232, i: 15, waiting for thread to join()
tid: 140113177143232, i: 16, waiting for thread to join()
tid: 140113177143232, i: 17, waiting for thread to join()
tid: 140113177143232, i: 18, waiting for thread to join()
tid: 140113177143232, i: 19, waiting for thread to join()
tid: 140113177143232, i: 20, waiting for thread to join()
tid: 140113177143232, i: 21, waiting for thread to join()
tid: 140113177143232, i: 22, waiting for thread to join()
tid: 140113177143232, i: 23, waiting for thread to join()
tid: 140113177143232, i: 24, waiting for thread to join()
tid: 140113177143232, i: 25, waiting for thread to join()
tid: 140113177143232, i: 26, waiting for thread to join()
PANIC
_

「スーパーキャンセル」を別のより明確な方法で伝えることができますか?もちろん、使用できるオブジェクトはtimerだけです。

シグナリングシャットダウン

timerオブジェクトには、操作するプロパティがあまりありません。ソケットのように、タイマーをある種の無効な状態にするために使用できるclose()などはありません。

ただし、有効期限があり、特別なドメイン値を使用して、アプリケーションの「無効」を通知できます。

_timer.get_io_service().post([](){
    std::cerr << "tid: " << std::this_thread::get_id() << ", cancelling in post\n";
    // also cancels:
    timer.expires_at(Timer::clock_type::time_point::min());
});
_

この「特別な値」は、完了ハンドラーで簡単に処理できます。

_void handle_timeout(const boost::system::error_code& ec)
{
    if (!ec) {
        started = true;
        if (timer.expires_at() != Timer::time_point::min()) {
            timer.expires_from_now(std::chrono::milliseconds(10));
            timer.async_wait(&handle_timeout);
        } else {
            std::cerr << "handle_timeout: detected shutdown\n";
        }
    } 
    else if (ec != boost::asio::error::operation_aborted) {
        std::cerr << "tid: " << std::this_thread::get_id() << ", handle_timeout error " << ec.message() << "\n";
    }
}
_
13
sehe