20

コンストラクターの実行に非常に長い時間がかかるオブジェクトを作成する必要がある場合があります。これにより、UI アプリケーションの応答性の問題が発生します。

そのため、オブジェクトが利用可能になったときに警告するコールバックを渡すことにより、非同期で呼び出されるように設計されたコンストラクターを作成することが賢明であるかどうか疑問に思っていました。

以下はサンプルコードです。

class C
{
public:
    // Standard ctor
    C()
    {
        init();
    }

    // Designed for async ctor
    C(std::function<void(void)> callback)
    {
        init();
        callback();
    }

private:
    void init() // Should be replaced by delegating costructor (not yet supported by my compiler)
    {
        std::chrono::seconds s(2);
        std::this_thread::sleep_for(s);
        std::cout << "Object created" << std::endl;
    }
};

int main(int argc, char* argv[])
{
    auto msgQueue = std::queue<char>();
    std::mutex m;
    std::condition_variable cv;
    auto notified = false;

    // Some parallel task
    auto f = []()
    {
        return 42;
    };

    // Callback to be called when the ctor ends
    auto callback = [&m,&cv,&notified,&msgQueue]()
    {
        std::cout << "The object you were waiting for is now available" << std::endl;
        // Notify that the ctor has ended
        std::unique_lock<std::mutex> _(m);
        msgQueue.push('x');
        notified = true;
        cv.notify_one();
    };

    // Start first task
    auto ans = std::async(std::launch::async, f);

    // Start second task (ctor)
    std::async(std::launch::async, [&callback](){ auto c = C(callback); });

    std::cout << "The answer is " << ans.get() << std::endl;

    // Mimic typical UI message queue
    auto done = false;
    while(!done)
    {
        std::unique_lock<std::mutex> lock(m);
        while(!notified)
        {
            cv.wait(lock);
        }
        while(!msgQueue.empty())
        {
            auto msg = msgQueue.front();
            msgQueue.pop();

            if(msg == 'x')
            {
                done = true;
            }
        }
    }

    std::cout << "Press a key to exit..." << std::endl;
    getchar();

    return 0;
}

このデザインに欠点はありますか?または、より良いアプローチがあるかどうか知っていますか?

編集

JoergB の回答のヒントに従って、同期または非同期の方法でオブジェクトを作成する責任を負うファクトリを作成しようとしました。

template <typename T, typename... Args>
class FutureFactory
{
public:
    typedef std::unique_ptr<T> pT;
    typedef std::future<pT> future_pT;
    typedef std::function<void(pT)> callback_pT;

public:
    static pT create_sync(Args... params)
    {
        return pT(new T(params...));
    }

    static future_pT create_async_byFuture(Args... params)
    {
        return std::async(std::launch::async, &FutureFactory<T, Args...>::create_sync, params...);
    }

    static void create_async_byCallback(callback_pT cb, Args... params)
    {
        std::async(std::launch::async, &FutureFactory<T, Args...>::manage_async_byCallback, cb, params...);
    }

private:
    FutureFactory(){}

    static void manage_async_byCallback(callback_pT cb, Args... params)
    {
        auto ptr = FutureFactory<T, Args...>::create_sync(params...);
        cb(std::move(ptr));
    }
};
4

7 に答える 7

17

あなたのデザインは非常に煩わしいようです。クラスがコールバックを認識しなければならない理由がわかりません。

何かのようなもの:

future<unique_ptr<C>> constructedObject = async(launchopt, [&callback]() {
      unique_ptr<C> obj(new C());
      callback();
      return C;
})

または単に

future<unique_ptr<C>> constructedObject = async(launchopt, [&cv]() {
      unique_ptr<C> ptr(new C());
      cv.notify_all(); // or _one();
      return ptr;
})

または単に(フューチャーなしで、コールバックが引数を取る):

async(launchopt, [&callback]() {
      unique_ptr<C> ptr(new C());
      callback(ptr);
})

同様に行うべきですよね?これらはまた、完全なオブジェクトが構築されたとき (C から派生したとき) にのみコールバックが呼び出されるようにします。

これらのいずれかを一般的な async_construct テンプレートにするのは、それほど手間がかかりません。

于 2013-02-04T11:37:46.173 に答える
9

問題をカプセル化します。非同期コンストラクターについては考えないでください。オブジェクトの作成をカプセル化する非同期メソッドについてのみ考えてください。

于 2013-02-04T11:09:14.620 に答える
4

std::futureメッセージキューを作成するのではなく、 使用する必要があるようです。std::future値を保持し、値のブロック、タイムアウト、またはポーリングを取得できるテンプレートクラスです。

std::future<int> fut = ans;
fut.wait();
auto result = fut.get();
于 2013-02-04T11:15:00.877 に答える
4

スレッドとシグナル ハンドラを使用したハックを提案します。

1) スレッドを生成して、コンストラクターのタスクを実行します。子スレッドと呼びましょう。このスレッドは、クラスの値を初期化します。

2) コンストラクターが完了すると、子スレッドは kill システム コールを使用して親スレッドにシグナルを送信します。(ヒント: SIGUSR1)。ASYNCHRONOUS ハンドラー呼び出しを受信したメイン スレッドは、必要なオブジェクトが作成されたことを認識します。

もちろん、作成中の複数のオブジェクトを区別するために、object-id などのフィールドを使用できます。

于 2013-02-04T11:19:28.263 に答える
3

私のアドバイス...

コンストラクターでこのような長い操作を行う必要がある理由をよく考えてください。

オブジェクトの作成を 3 つの部分に分割した方がよい場合がよくあります

a) 割り当て b) 構築 c) 初期化

小さなオブジェクトの場合、3 つすべてを 1 つの「新しい」操作で実行するのが理にかなっています。ただし、重いオブジェクトは、どうしてもステージを分離したいものです。必要なリソースの量を把握し、割り当てます。メモリ内のオブジェクトを有効であるが空の状態に構築します。

次に...すでに有効であるが空のオブジェクトに長いロード操作を行います。

ずいぶん前に本を読んでこのパターンを覚えたと思いますが (おそらくスコット・マイヤーズ?)、あらゆる種類の問題を解決してくれるので、強くお勧めします。たとえば、オブジェクトがグラフィック オブジェクトの場合、必要なメモリ量を把握します。失敗した場合は、できるだけ早くユーザーにエラーを表示します。そうでない場合は、オブジェクトを未読としてマークします。次に、それを画面に表示したり、ユーザーが操作したりできます。オブジェクトを非同期ファイルロードで初期化し、完了したら、オブジェクトに「ロード済み」というフラグを設定します。更新関数がロードされたことを確認すると、グラフィックを描画できます。

また、オブジェクト A がオブジェクト B を必要とする構築順序などの問題にも本当に役立ちます。B の前に A を作成する必要があることに突然気付きます。シンプルに、空の B を作成し、それを参照として渡します。A が be が空であることを認識できるほど賢く、それを使用する前に空でなくなるのを待つ限り、すべて問題ありません。

そして...忘れないでください..破壊では反対のことができます。最初にオブジェクトを空としてマークし、新しいオブジェクトを使用しないようにします (初期化解除) リソースを解放します (破棄) 次に、メモリを解放します (割り当て解除)

同じ特典が適用されます。

于 2013-02-04T17:39:11.613 に答える
2

部分的に初期化されたオブジェクトがあると、初期化されているかどうかを確認する必要があるため、バグや不必要に複雑なコードが発生する可能性があります。

UI と処理には別のスレッドを使用し、スレッド間の通信にはメッセージ キューを使用することをお勧めします。UI を処理するためだけに UI スレッドを残しておくと、常に応答性が向上します。

オブジェクトの作成を要求するメッセージを、ワーカー スレッドが待機するキューに入れます。オブジェクトが作成された後、ワーカーは、オブジェクトの準備ができたことを示すメッセージを UI キューに入れることができます。

于 2013-02-04T11:24:57.653 に答える
0

考慮すべきもう 1 つのパターンを次に示します。future<> で wait() を呼び出しても無効にならないという事実を利用しています。したがって、get() を呼び出さない限り、安全です。このパターンのトレードオフは、メンバー関数が呼び出されるたびにwait() を呼び出すという厄介なオーバーヘッドが発生することです。

class C
{
    future<void> ready_;

public:
    C()
    {
        ready_ = async([this]
        {
            this_thread::sleep_for(chrono::seconds(3));
            cout << "I'm ready now." << endl;
        });
    }

    // Every member function must start with ready_.wait(), even the destructor.

    ~C(){ ready_.wait(); }

    void foo()
    {
        ready_.wait();

        cout << __FUNCTION__ << endl;
    }
};

int main()
{
    C c;

    c.foo();

    return 0;
}
于 2013-02-05T23:39:13.927 に答える