2017-03-02 1 views
5

Grundsätzlich muss ich die gleiche Sache, die wie folgt in Java getan wird:Wie lade ich die Lambda-Funktion in die Ereignisschleife von Qt?

SwingUtilities.invokeLater(()->{/* function */}); 

Oder wie dies in javascript:

setTimeout(()=>{/* function */}, 0); 

Aber mit Qt und Lambda. So ein Pseudocode:

Als eine zusätzliche Komplikation, ich brauche dies in Multithread-Kontext zu arbeiten. Ich versuche tatsächlich, bestimmte Methoden automatisch im richtigen Thread auszuführen. Wie der Code dann aussehen würde:

SomeClass::threadSafeAsyncMethod() { 
    if(this->thread() != QThread::currentThread()) { 
     Qt::queuePushMagic([this]()=>{ this->threadSafeAsyncMethod() }); 
     return; 
    } 
} 

Wie geht das?

+0

Haben Sie 'QTimer' probiert? – MrEricSir

+0

Das könnte funktionieren, denke ich. Aber eine Sache zu erwähnen, und eine ziemlich wichtige, ist, dass ich versuche, die ** Ereignisschleife eines anderen Threads einzuschalten. –

Antwort

11

Ihr Problem ist von How to leverage Qt to make a QObject method thread-safe? Lassen Sie uns die dort angebotenen Lösungen Ihrem Anwendungsfall anpassen. Lassen Sie uns zunächst die Sicherheitsprüfung ausklammern:

bool isSafe(QObject * obj) { 
    Q_ASSERT(obj->thread() || qApp && qApp->thread() == QThread::currentThread()); 
    auto thread = obj->thread() ? obj->thread() : qApp->thread(); 
    return thread == QThread::currentThread(); 
} 

Der Ansatz, den Sie vorgeschlagen nimmt einen Funktor, und lässt den Compiler befassen sich mit den Argumenten packen (falls vorhanden) im Funktors:

template <typename Fun> void postCall(QObject * obj, Fun && fun) { 
    qDebug() << __FUNCTION__; 
    struct Event : public QEvent { 
     using F = typename std::decay<Fun>::type; 
     F fun; 
     Event(F && fun) : QEvent(QEvent::None), fun(std::move(fun)) {} 
     Event(const F & fun) : QEvent(QEvent::None), fun(fun) {} 
     ~Event() { fun(); } 
    }; 
    QCoreApplication::postEvent(
      obj->thread() ? obj : qApp, new Event(std::forward<Fun>(fun))); 
} 

A zweiter Ansatz speichert die Kopien aller Parameter explizit im Rahmen der Veranstaltung und verwendet keine Funktors:

template <typename Class, typename... Args> 
struct CallEvent : public QEvent { 
    // See https://stackoverflow.com/a/7858971/1329652 
    // See also https://stackoverflow.com/a/15338881/1329652 
    template <int ...> struct seq {}; 
    template <int N, int... S> struct gens { using type = typename gens<N-1, N-1, S...>::type; }; 
    template <int ...S>  struct gens<0, S...> { using type = seq<S...>; }; 
    template <int ...S>  void callFunc(seq<S...>) { (obj->*method)(std::get<S>(args)...); } 
    Class * obj; 
    void (Class::*method)(Args...); 
    std::tuple<typename std::decay<Args>::type...> args; 
    CallEvent(Class * obj, void (Class::*method)(Args...), Args&&... args) : 
     QEvent(QEvent::None), obj(obj), method(method), args(std::move<Args>(args)...) {} 
    ~CallEvent() { callFunc(typename gens<sizeof...(Args)>::type()); } 
}; 

template <typename Class, typename... Args> void postCall(Class * obj, void (Class::*method)(Args...), Args&& ...args) { 
    qDebug() << __FUNCTION__; 
    QCoreApplication::postEvent(
      obj->thread() ? static_cast<QObject*>(obj) : qApp, new CallEvent<Class, Args...>{obj, method, std::forward<Args>(args)...}); 
} 

Es verwendet wird, wie folgt:

struct Class : QObject { 
    int num{}; 
    QString str; 
    void method1(int val) { 
     if (!isSafe(this)) 
     return postCall(this, [=]{ method1(val); }); 
     qDebug() << __FUNCTION__; 
     num = val; 
    } 
    void method2(const QString &val) { 
     if (!isSafe(this)) 
     return postCall(this, &Class::method2, val); 
     qDebug() << __FUNCTION__; 
     str = val; 
    } 
}; 

Ein Test-Harnisch:

// https://github.com/KubaO/stackoverflown/tree/master/questions/safe-method-40382820 
#include <QtCore> 

// above code 

class Thread : public QThread { 
public: 
    Thread(QObject * parent = nullptr) : QThread(parent) {} 
    ~Thread() { quit(); wait(); } 
}; 

void moveToOwnThread(QObject * obj) { 
    Q_ASSERT(obj->thread() == QThread::currentThread()); 
    auto thread = new Thread{obj}; 
    thread->start(); 
    obj->moveToThread(thread); 
} 

int main(int argc, char ** argv) { 
    QCoreApplication app{argc, argv}; 
    Class c; 
    moveToOwnThread(&c); 

    const auto num = 44; 
    const auto str = QString::fromLatin1("Foo"); 
    c.method1(num); 
    c.method2(str); 
    postCall(&c, [&]{ c.thread()->quit(); }); 
    c.thread()->wait(); 
    Q_ASSERT(c.num == num && c.str == str); 
} 

Ausgang:

postCall 
postCall 
postCall 
method1 
method2 

Die oben kompiliert und arbeitet entweder mit Qt 4 oder Qt 5.

Siehe auch this question, verschiedene Möglichkeiten zu erkunden Aufruf von Funktoren in anderen Thread-Kontexten in Qt.

+0

Das sieht gut aus. Ich spielte bereits mit anderen Vorschlägen und stolperte über ein weiteres Problem - was ist, wenn der Thread * noch nicht läuft *? Es könnte ein Problem in meinem Design sein. Was ich mache, ist, dass ich 'this-> moveToThread (this)' in der neu implementierten Version von 'QThread :: run' nenne. Ich bin mir nicht einmal sicher, ob dieses Problem zusammenhängt oder ob ich es falsch mache. –

+2

Die Ereignisse, die an ein bestimmtes Objekt gesendet werden, verfolgen das Objekt, während es die Threads wechselt. Das ist also kein Problem. Aber 'this-> moveToThread (this)' ist ein Anti-Pattern. Sie müssen 'QThread' nicht berühren. Setzen Sie Ihren Code in ein 'QObject' und verschieben Sie ihn in einen bereits laufenden Thread. –

+0

Also sollte ich 'QThread' Unterklassen nicht beibehalten, die dann in dem Thread laufen, den sie darstellen? Ich dachte, es funktioniert so. Ich könnte natürlich den Thread separat machen, aber ich dachte, das ist nur mehr Code. –

Verwandte Themen