GCD を使用した ObjC では、ラムダを実行する方法がありますイベント ループをスピンするいずれかのスレッド内。例:
dispatch_sync(dispatch_get_main_queue(), ^{ /* do sth */ });
または:
dispatch_async(dispatch_get_main_queue(), ^{ /* do sth */ });
メインスレッドの中で何かを実行します (C の []{ / do sth / } に相当)
で同じことを行うにはどうすればよいですか? Qt?
私が読んだ限りでは、解決策は何らかの方法でメインスレッドのオブジェクトにシグナルを送信することだと思います。しかし、何の物体でしょうか? QApplication::instance() だけですか? (その時点でメインスレッドに存在する唯一のオブジェクトです。) そして、どのようなシグナルでしょうか?
それは確かに可能です。どのソリューションも、ファンクターをラップするイベントを、目的のスレッドに存在するコンシューマー オブジェクトに配信することに重点を置きます。この操作をメタコール ポスティングと呼びます。詳細はいくつかの方法で実行できます。
// invoke on the main thread QMetaObject::invokeMethod(qApp, []{ ... }); // invoke on an object's thread QMetaObject::invokeMethod(obj, []{ ... }); // invoke on a particular thread QMetaObject::invokeMethod(QAbstractEventDispatcher::instance(thread), []{ ... });
// https://github.com/KubaO/stackoverflown/tree/master/questions/metacall-21646467 // Qt 5.10 & up - it's all done template <typename F> static void postToObject(F &&fun, QObject *obj = qApp) { QMetaObject::invokeMethod(obj, std::forward<F>(fun)); } template <typename F> static void postToThread(F && fun, QThread *thread = qApp->thread()) { auto *obj = QAbstractEventDispatcher::instance(thread); Q_ASSERT(obj); QMetaObject::invokeMethod(obj, std::forward<F>(fun)); } // Qt 5/4 - preferred, has least allocations namespace detail { template <typename F> struct FEvent : public QEvent { using Fun = typename std::decay<F>::type; Fun fun; FEvent(Fun && fun) : QEvent(QEvent::None), fun(std::move(fun)) {} FEvent(const Fun & fun) : QEvent(QEvent::None), fun(fun) {} ~FEvent() { fun(); } }; } template <typename F> static void postToObject(F && fun, QObject * obj = qApp) { if (qobject_cast<QThread*>(obj)) qWarning() << "posting a call to a thread object - consider using postToThread"; QCoreApplication::postEvent(obj, new detail::FEvent<F>(std::forward<F>(fun))); } template <typename F> static void postToThread(F && fun, QThread * thread = qApp->thread()) { QObject * obj = QAbstractEventDispatcher::instance(thread); Q_ASSERT(obj); QCoreApplication::postEvent(obj, new detail::FEvent<F>(std::forward<F>(fun))); }
void test1() { QThread t; QObject o; o.moveToThread(&t); // Execute in given object's thread postToObject([&]{ o.setObjectName("hello"); }, &o); // or postToObject(std::bind(&QObject::setObjectName, &o, "hello"), &o); // Execute in given thread postToThread([]{ qDebug() << "hello from worker thread"; }); // Execute in the main thread postToThread([]{ qDebug() << "hello from main thread"; }); }
// Qt 5/4 template <typename T, typename R> static void postToObject(T * obj, R(T::* method)()) { struct Event : public QEvent { T * obj; R(T::* method)(); Event(T * obj, R(T::*method)()): QEvent(QEvent::None), obj(obj), method(method) {} ~Event() { (obj->*method)(); } }; if (qobject_cast<QThread*>(obj)) qWarning() << "posting a call to a thread object - this may be a bug"; QCoreApplication::postEvent(obj, new Event(obj, method)); } void test2() { QThread t; struct MyObject : QObject { void method() {} } obj; obj.moveToThread(&t); // Execute in obj's thread postToObject(&obj, &MyObject::method); }
上記のメソッドはすべて、イベント ループを持たないスレッドから動作します。 QTBUG-66458 のため、QTimer::singleShot の便利な流用には、ソース スレッドにもイベント ループが必要です。そうすれば、postToObject は非常にシンプルになり、おそらく QTimer::singleShot を直接使用することもできます。ただし、このイディオムに慣れていない人には意図が見えにくい厄介な名前です。型チェックが必要ない場合でも、意図をより適切に示すために名前を付けた関数による間接化は意味があります。
template <typename F> static void postToObject(F && fun, QObject * obj = qApp) { if (qobject_cast<QThread*>(obj)) qWarning() << "posting a call to a thread object - consider using postToThread"; QTimer::singleShot(0, obj, std::forward<F>(fun)); }
という観点から問題を定義しましょう。以下の共通コード。最も単純な解決策は、ターゲット スレッドがメイン スレッドの場合はアプリケーション オブジェクト、または他の特定のスレッドのイベント ディスパッチャにイベントをポストします。イベント ディスパッチャーは QThread::run が入力された後にのみ存在するため、needsRunningThread から true を返すことでスレッドが実行されている必要があることを示します。
#ifndef HAS_FUNCTORCALLCONSUMER namespace FunctorCallConsumer { bool needsRunningThread() { return true; } QObject * forThread(QThread * thread) { Q_ASSERT(thread); QObject * target = thread == qApp->thread() ? static_cast<QObject*>(qApp) : QAbstractEventDispatcher::instance(thread); Q_ASSERT_X(target, "postMetaCall", "the receiver thread must have an event loop"); return target; } } #endif
最も単純な形式のメタコール投稿関数、ファンクター呼び出しコンシューマーが特定のスレッドにオブジェクトを提供し、ファンクター呼び出しイベントをインスタンス化するように要求します。イベントの実装はまだ先ですが、これがさまざまな実装の本質的な違いです。
#ifndef HAS_POSTMETACALL void postMetaCall(QThread * thread, const std::function<void()> & fun) { auto receiver = FunctorCallConsumer::forThread(thread); QCoreApplication::postEvent(receiver, new FunctorCallEvent(fun, receiver)); } void postMetaCall(QThread * thread, std::function<void()> && fun) { auto receiver = FunctorCallConsumer::forThread(thread); QCoreApplication::postEvent(receiver, new FunctorCallEvent(std::move(fun), receiver)); } #endif
デモンストレーションの目的で、ワーカー スレッドは最初にメタコールをメイン スレッドにポストし、その後 QThread に準拠します。 ::run() を使用してイベント ループを開始し、他のスレッドからのメタコールの可能性をリッスンします。ミューテックスは、コンシューマーの実装で必要な場合に、スレッド ユーザーがスレッドの開始を簡単な方法で待機できるようにするために使用されます。このような待機は、上記のデフォルトのイベント コンシューマーに必要です。
dispatch_sync(dispatch_get_main_queue(), ^{ /* do sth */ });
以上がGCD と同様に、Qt の特定のスレッドでファンクターまたはラムダを実行するにはどうすればよいですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。