2016-07-27 2 views
1

L'histoire:

Je fais usage de l'API QtConcurrent pour chaque "longue" opération dans mon application. Cela fonctionne plutôt bien, mais je rencontre des problèmes avec la création de QObjects.Un moyen de détecter si un QObject appartient à un QThread "mort"?

Considérez ce morceau de code, qui utilise un fil pour créer un « Foo » objet:

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

Il fonctionne bien, mais si la classe est dérivée de la classe QObject « Foo », le « résultat » L'instance appartient à QThread qui a créé l'objet.

Donc, pour utiliser le signal correctement/emplacement avec l'instance « résultat », il faut faire quelque chose comme ceci:

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

Maintenant, tous les travaux que exepected, et je pense que cela est le comportement normal et la valeur nominale Solution.

Le problème:

J'ai beaucoup de ce genre de code, qui créent parfois des objets, qui peuvent également créer des objets. La plupart d'entre eux sont créés correctement avec un appel "moveToThread".

Mais parfois, un appel "moveToThread" me manque.

Et puis, beaucoup de choses semblent ne pas fonctionner (parce que ces emplacements d'objets sont "cassés"), sans aucun avertissement Qt.

Maintenant, je passe parfois beaucoup de temps à comprendre pourquoi quelque chose ne fonctionne pas, avant de comprendre que c'est uniquement parce que les emplacements ne sont plus appelés sur une instance d'objet particulière.

La question:

Est-il possible de me aider à prévenir/detect/debug ce genre de situation? Par exemple:

  • ayant un avertissement connecté à chaque fois qu'un QThread est supprimé, mais il y a des objets vivants qui lui appartient?
  • Avoir un avertissement enregistré chaque fois qu'un signal est émis à un objet QThread est supprimé?
  • Avoir un avertissement enregistré chaque fois qu'un signal est émis sur un objet (dans un autre thread) et non traité avant un timeout?

Merci

+0

Y at-il une raison que le 'type foo' 'QObject 's doivent avoir une affinité de thread non-principal (ce que je pense que vous voulez dire par" appartient à ")? – eclarkso

+0

La troisième question est un doublon; veuillez l'enlever. Vous demandez comment détecter une boucle d'événement bloquée - ceci est indépendant de l'émission d'un signal. Voir [ici] (http://stackoverflow.com/q/25038829/1329652). –

Répondre

2

Il est possible de suivre le mouvement d'un objet entre les threads. Juste avant qu'un objet soit déplacé vers le nouveau thread, un événement ThreadChange lui est envoyé. Vous pouvez filtrer cet événement et faire exécuter votre code pour prendre note du moment où un objet quitte un thread. Mais il est trop tôt pour savoir si l'objet va quelque part. Pour détecter cela, vous devez publier un métacall (voir this question) dans la file d'attente de l'objet à exécuter dès que le traitement des événements de l'objet reprend dans le nouveau thread. Vous pouvez également vous connecter à QThread::finished pour avoir une chance de parcourir votre liste d'objets et de vérifier si l'un d'entre eux vit sur le thread qui est sur le point de mourir.Mais tout cela est assez impliqué: chaque thread aura besoin de son propre objet tracker/filter, car les filtres d'événements doivent vivre dans le thread de l'objet. Nous parlons probablement de plus de 200 lignes de code pour le faire correctement, en manipulant tous les cas de coin.

Au lieu de cela, vous pouvez tirer parti RAII et maintenez vos objets à l'aide des poignées qui gèrent l'affinité de fil comme une ressource (car il est l'un!):

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

Vous pouvez retourner une MainResult en valeur, mais le type de retour du foncteur doit être explicitement donné:

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

vous pouvez retourner le résultat de l'appel MainResult; C'est un foncteur lui-même pour sauver un peu de frappe mais cela pourrait être considéré comme un hack et peut-être vous devriez convertir operator()() en une méthode avec un nom court.

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

Bien qu'il soit préférable de construire l'objet avec la poignée, il est également possible de passer un pointeur d'instance au constructeur de la poignée:

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

L'utilisation de poignées gérant l'affinité des threads est probablement la meilleure idée dans cette situation. Merci. – Aurelien