2013-02-04 39 views
20

時々、コンストラクタの実行に非常に時間がかかるオブジェクトを作成する必要があります。 これは、UIアプリケーションの応答性の問題につながります。オブジェクトが利用可能になったときに私を警告することへのコールバックを渡すことによって、非同期的に呼び出されるように設計コンストラクタを書くことは賢明なことができれば C++ 11の非同期コンストラクタ

は、だから私は思っていました。以下は

はサンプルコードです:

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の答えのヒントに続い

EDIT

、私は、同期または非同期の方法でオブジェクトを作成する責任を負う工場を書き込もうとしました:

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)); 
    } 
}; 
+0

コンストラクタ内でstd :: asyncを使用してみましたか?私はあなたがコールバックに非同期を入れて、クラス自体のメンバーとして結果を格納することができると思います。 – thang

+0

@thang私はそれを試してみたいと思います...私にとっての問題は、オブジェクトを作成しても、まだ使用する準備ができていないということです。この場合、isValid()メソッドが役立ちます。 – Cristiano

+0

ええ、あなたはisValidやwaitValidなどを追加することができます。そういうわけで、すべてがクラスにカプセル化されています...同じ機能、ちょっとちょっとだけ。 – thang

答えて

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テンプレートにするのはあまり手間ではありません。

+0

さて、UIスレッドにオブジェクトが準備完了であることを通知するには、同期が必要です。最後の解決方法は、すべての責任をコールバックに委ねます。これにより、ロックフリーのシグナリングも可能になります。他の方法は、結果を「未来」に移送し、それをシグナリングから分離する。もちろん、シグナリングとスレッド出口との間にはギャップがあり、これにより「未来」が準備されます。しかし、コールバックでのロックの使用は同様の効果があります。メインスレッドがそのロックでブロックする可能性のある時間があります。 – JoergB

9

はあなたの問題をカプセル化。非同期コンストラクタは、オブジェクトの生成をカプセル化する非同期メソッドだけを考えないでください。

+0

あなたは非同期工場のようなものをお勧めしますか? – Cristiano

+2

@Cristianoこれはオプションです。私は実際には、非同期性がネイティブのコンストラクタ自体に結びつくのが好きではないと言っていました。 –

+4

これは第2です。オブジェクトを作成した場合、通常のエクスペクトは、コンストラクターが返すときに完全に構​​築されたものであり、他のものではありません。コンストラクタがスローするか、有効なオブジェクトがあり、推測がありません。一方、オブジェクトを構築する非同期タスクを(同期的に、その観点から)作成することには何も問題はありません。 – Damon

4

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

std::future<int> fut = ans; 
fut.wait(); 
auto result = fut.get(); 
+0

メッセージキューは雑音だけです...典型的なUIメッセージループを模倣するだけです。 – Cristiano

+0

これは、作成を保留中の複数のオブジェクトがある場合を除いて良いことです...待機するとブロックされます。 – thang

+0

@thangはい、これは 'std :: future'の欠点です。 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3428.pdfは 'when_any'を提案する。多くの先物ライブラリには似たようなものがありますが、プリミティブ(@ Cristianoの条件変数など)から一緒に置くこともできます。 – ecatmur

4

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

1)スレッドを生成してコンストラクタのタスクを実行します。それを子スレッドと呼ぶことができます。このスレッドは、クラス内の値を初期化します。コンストラクタが完了した後

2)、子スレッドは親スレッドに信号を送信するためにキルシステムコールを使用しています。 (ヒント:SIGUSR1)。 ASYNCHRONOUSハンドラー呼び出しを受け取った主スレッドは、必要なオブジェクトが作成されたことを知ります。

勿論、あなたは、作成中の複数のオブジェクトを区別するために、オブジェクトIDのようなフィールドを使用することができます。あなたは、彼らが初期化されたりしていないかどうかを確認しなければならないので、

+2

シグナルのものを除いて、これはまさに私のサンプルコードがするものです。 – Cristiano

2

持つ部分的に初期化されたオブジェクトは、バグや不必要に複雑なコードにつながる可能性があります。

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

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

+0

あなたは正しいです。これは、別のスレッドでオブジェクトを作成する理由です。これはまさに私がやりたいことですが、std :: async機能を使用しているので、私は "明示的"スレッドを持っていません。 – Cristiano

+0

明らかに、std :: asyncで起動されたasync ctorからUIメッセージキューにメッセージを挿入することができます。そのため、物事をコントロールしたいかどうかは疑問です。たとえば、2つの非同期ctorsを起動する:それらの両方を並列(2つのスレッド)で実行しても構わないのですか?そして、あなたはstd :: futureをチェックする必要はありません。 –

4

私のアドバイス...

あなたは、コンストラクタで、このような長い操作を行う必要がある理由について慎重に考えてみてください。小さなオブジェクトのための3つの部分に私はオブジェクトの作成を分割した方がよいことが多い見つける

a)に割り当て b)の建設 c)の初期化

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

次に、既に有効な、しかし空のオブジェクトにあなたの長いロード操作を行います。

私はずっと前にこの本を読んだことがあると思いますが(スコット・マイヤーズのようなものでしょうか?)、私はそれを強く推奨し、あらゆる問題を解決します。たとえば、オブジェクトがグラフィックオブジェクトの場合、必要なメモリ量を把握します。失敗した場合は、できるだけ早くエラーを表示してください。そうでない場合、オブジェクトはまだ読み取られていないとマークします。 非同期ファイルロードでオブジェクトを初期化します。完了すると、オブジェクトに "loaded"というフラグを設定します。更新機能がロードされたことを確認すると、グラフィックが描画されます。

また、オブジェクトAがオブジェクトBを必要とする工事順序などの問題にも本当に役立ちます。突然、Bの前にAを作る必要があることがわかります。シンプルで、空のBを作り、リファレンスとして渡してください。Aが空であることを知るのに十分賢い限り、それが使用される前ではなく、すべてがうまくいきます。

そして...忘れないで...あなたは破壊の反対をすることができます。 マーク、空の第一として、あなたのオブジェクトは、それほど新しいものでは は次にメモリ(解放)と同じメリットが適用されます

を解放すること(脱初期化) 無料資源、(破壊)を使用していません。

+0

これは賢明な提案です。共有いただきありがとうございます! – Cristiano

0

もう1つのパターンを検討します。将来のwait()の呼び出しが無効にならないという事実を利用しています。だから、get()を呼び出さない限り、あなたは安全です。このパターンのトレードオフは、メンバー関数が呼び出されるたびにwait()を呼び出すという膨大なオーバーヘッドが発生することです。

+0

私はこの方法で少し恐ろしい気がする。なぜなら、すべての方法の開始を待つことを忘れてしまうと、悪いことが起きる可能性があるからです。 さらに、これにより、最初のメソッドを呼び出すときに呼び出し元がブロックされ、後続の他のメソッドを呼び出すときに不要なオーバーヘッドが発生します。 – Cristiano

関連する問題