2017-03-02 58 views
5

基本上我需要是這樣用Java做同樣的事情:如何將lambda函數排入Qt的事件循環?

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

或者這樣在javascript:

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

但隨着Qt和拉姆達。所以一些僞代碼:

Qt::queuePushMagic([]() { /* function */ }); 

作爲一個額外的複雜,我需要這個工作在多線程的上下文。我實際上試圖做的是自動運行某些方法在正確的線程。代碼會看起來如何:

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

如何做到這一點?

+0

您是否試過'QTimer'? – MrEricSir

+0

這可以工作,我猜。但有一點需要提及,而且更重要的一點是,我試圖推動**其他線程的**事件循環。 –

回答

11

您的問題是How to leverage Qt to make a QObject method thread-safe?讓我們調整您提供的解決方案以適應您的使用案例。首先,讓我們分解出的安全檢查:

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(); 
} 

你所建議的做法需要一個函子,並讓編譯器處理的仿函數中包裝起來的參數(如果有的話):

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

一第二種方法存儲明確事件中的所有參數的副本,並且不使用函子:

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)...}); 
} 

它的使用方法如下:

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; 
    } 
}; 

甲測試工具:

// 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); 
} 

輸出:

postCall 
postCall 
postCall 
method1 
method2 

上述編譯和可與任一Qt的4或5的Qt

也見this question,探索各種方式在Qt的其他線程上下文中調用函子。

+0

這看起來不錯。我已經在玩其他提案,並且偶然發現了另外一個問題 - 如果該線程沒有運行*但是*會怎麼樣?這可能是我設計中的問題。我所做的是我在'QThread :: run'的重新實現版本中調用'this-> moveToThread(this)'。我甚至不確定這個問題是否相關,或者我是否做錯了。 –

+2

發佈到給定對象的事件在切換線程時跟蹤對象,所以這不是問題。但'this-> moveToThread(this)'是一種反模式。你不需要碰'QThread'。把你的代碼放入一個'QObject'中,並將它移動到已經運行的線程中。 –

+0

所以我不應該維護'QThread'子類,然後運行在他們代表的線程中?我認爲它的工作原理就是這樣。我當然可以單獨製作線程,但我認爲這只是更多的代碼。 –