2016-07-27 19 views
1

Die Geschichte:

Ich benutze die QtConcurrent API für jede "lange" Operation in meiner Anwendung. Es funktioniert ziemlich gut, aber ich habe einige Probleme mit der Erstellung von QObjects.Gibt es eine Möglichkeit festzustellen, ob ein QObject zu einem "toten" QThread gehört?

Betrachten Sie dieses Stück Code, der einen Thread verwenden, um ein „Foo“ Objekt zu erstellen:

QFuture<Foo*> = QtConcurrent::run([=]() 
{ 
    Data* data = /*long operation to acquire the data*/ 
    Foo* result = new Foo(data); 
    return result; 
}); 

Es funktioniert gut, aber wenn die „Foo“ Klasse von QObject Klasse abgeleitet ist, das „Ergebnis“ Instanz gehört zu dem QThread, der das Objekt erstellt hat.

So richtig zu verwenden Signal/Slot mit der „Ergebnis“ So sollte man so etwas tun:

QFuture<Foo*> = QtConcurrent::run([=]() 
{ 
    Data* data = /*long operation to acquire the data*/ 
    Foo* result = new Foo(data); 
    // Move "result" to the main application thread 
    result->moveToThread(qApp->thread()); 
    return result; 
}); 

nun alle Arbeiten wie exepected, und ich denke, das ist das normale Verhalten ist und die nominale Lösung.

Das Problem:

Ich habe eine Menge von dieser Art von Code, die manchmal Objekte erstellen, die auch Objekte erstellen können. Die meisten von ihnen werden ordnungsgemäß mit einem "moveToThread" -Aufruf erstellt.

Aber manchmal vermisse ich einen "moveToThread" Anruf.

Und dann, viele Dinge sehen aus wie sie nicht funktionieren (weil diese Objektsteckplätze "gebrochen" sind), ohne jede Qt-Warnung.

Jetzt verbringe ich manchmal viel Zeit, um herauszufinden, warum etwas nicht funktioniert, bevor ich verstehe, nur weil die Slots nicht mehr für eine bestimmte Objektinstanz aufgerufen werden.

Die Frage:

Gibt es eine Möglichkeit, mir zu helfen diese Art von Situation zu verhindern/detect/debug? Zum Beispiel:

  • eine Warnung jedes Mal ein QThread protokolliert, die gelöscht wird, aber es gibt Objekte lebendig, die zu ihr gehört?
  • Wird jedes Mal eine Warnung protokolliert, wenn ein Signal an ein Objekt gesendet wird, das QThread gelöscht wurde?
  • eine Warnung jedes Mal protokolliert wird, wenn ein Signal an ein Objekt (in einem anderen Thread) gesendet und nicht vor einem Timeout verarbeitet wird?

Dank

+0

Gibt es einen Grund, dass die 'foo' Typ 'QObject 's müssen Nicht-Haupt-Thread-Affinität haben (was ich denke, du meinst mit" gehört zu ")? – eclarkso

+0

Die dritte Frage ist ein Duplikat; Bitte entferne es. Sie fragen, wie man eine Hung-Event-Schleife erkennt - unabhängig davon, ob ein Signal ausgegeben wird. Siehe [hier] (http://stackoverflow.com/q/25038829/1329652). –

Antwort

2

Es ist möglich, eine Objektbewegung zwischen Threads zu verfolgen. Kurz bevor ein Objekt in den neuen Thread verschoben wird, wird ein Ereignis ThreadChange gesendet. Sie können dieses Ereignis filtern und Ihren Code ausführen lassen, um zu notieren, wenn ein Objekt einen Thread verlässt. Aber es ist zu früh, um zu wissen, ob das Objekt irgendwo hingeht. Um dies zu erkennen, müssen Sie einen Metacall (siehe this question) in die Warteschlange des Objekts schreiben, der ausgeführt wird, sobald die Ereignisverarbeitung des Objekts im neuen Thread fortgesetzt wird. Sie können auch an QThread::finished anhängen, um eine Chance zu bekommen, durch Ihre Objektliste zu schauen und zu überprüfen, ob einer von ihnen auf dem Thread lebt, der im Begriff ist zu sterben.

Aber all dies ist ziemlich beteiligt: ​​Jeder Thread benötigt ein eigenes Tracker/Filter-Objekt, da Ereignisfilter im Thread des Objekts leben müssen. Wir sprechen wahrscheinlich von mehr als 200 Codezeilen, um alles richtig zu machen und alle Eckfälle zu bearbeiten.

Stattdessen können Sie RAII nutzen und halten Sie Ihre Objekte Griffe verwenden, die Threadaffinität als Ressource zu verwalten (weil es ist!):

// https://github.com/KubaO/stackoverflown/tree/master/questions/thread-track-38611886 
#include <QtConcurrent> 

template <typename T> 
class MainResult { 
    Q_DISABLE_COPY(MainResult) 
    T * m_obj; 
public: 
    template<typename... Args> 
    MainResult(Args&&... args) : m_obj{ new T(std::forward<Args>(args)...) } {} 
    MainResult(T * obj) : m_obj{obj} {} 
    T* operator->() const { return m_obj; } 
    operator T*() const { return m_obj; } 
    T* operator()() const { return m_obj; } 
    ~MainResult() { m_obj->moveToThread(qApp->thread()); } 
}; 

struct Foo : QObject { Foo(int) {} }; 

Sie einen MainResult Wert zurückgeben kann, aber die Art Rückkehr der Funktors muss explizit angegeben werden:

QFuture<Foo*> test1() { 
    return QtConcurrent::run([=]()->Foo*{ // explicit return type 
     MainResult<Foo> obj{1}; 
     obj->setObjectName("Hello"); 
     return obj; // return by value 
    }); 
} 

Alternativ Sie das Ergebnis des Aufrufs MainResult zurückkehren können; Es ist ein Funktor selbst, etwas Tipparbeit zu sparen, aber dies könnte als Hack betrachtet werden und vielleicht sollten Sie operator()() in eine Methode mit einem Kurznamen umwandeln.

QFuture<Foo*> test2() { 
    return QtConcurrent::run([=](){ // deduced return type 
     MainResult<Foo> obj{1}; 
     obj->setObjectName("Hello"); 
     return obj(); // return by call 
    }); 
} 

Während es bevorzugt, das Objekt zusammen mit dem Griff zu konstruieren, ist es auch möglich, eine Instanz Zeiger an den Konstruktor der Griff weitergeben müssen:

MainResult<Foo> obj{ new Foo{1} }; 
+0

Die Verwendung von Handles, die die Thread-Affinität verwalten, ist wahrscheinlich die beste Idee in dieser Situation. Vielen Dank. – Aurelien