2016-08-30 6 views
1

Viele ähnliche Fragen existieren, aber keine passende Antwort gefunden.Qt: Signal Haupt-Thread

Ich benutze eine 3rd-Party-Bibliothek.

Wenn einige der virtuellen Methoden in den Klassen von lib aufgerufen werden, werden diese von einem Worker-Thread aufgerufen, der nicht von meiner Anwendung gestartet wurde. Dieser Thread ist kein QThread, noch könnte er jemals sein.

Ich kann von diesem Thread ausgeben, aber nur wenn ich den Steckplatz mit Qt :: DirectConnection verbinden. Das Ergebnis ist, dass QObject :: sender() im SLOT immer NULL zurückgibt. Ich möchte zum Beispiel deleteLater() aufrufen, aber das kann nur in einem QThread geplant werden.

Ich denke, ich muss zurück zum Haupt-Thread, aber wie kann ich das Objekt auf dem Haupt-Thread signalisieren?

Beispiel: Wenn die unten genannte Methode aufgerufen wird, geschieht dies in einem Thread, der von einer Drittanbieterbibliothek erstellt wurde.

/*virtual*/ bool MediaPlayer::onEof() 
{ 
    stopTransmit(); 
    emit sigFinished(); // slots only called if bound using Qt::DirectConnection 
    deleteLater();  // dtor is never called 

    return false; 
} 

Die Verbindung wird aus einem nicht-QThread als Ganzes zu beziehen auch wie folgt:

/*virtual*/ void 
SipCall::state_answer_call::onEntering(SipCall& ref) 
{ 
    ... 
    MediaPlayer* player = new MediaPlayer; 
    ref.connect(player, SIGNAL(sigFinished()), SLOT(slotMediaFinished()), Qt::DirectConnection); 
    ... 
} 

Ohne ausdrückliche Qt::DirectConnection wird SipCall::slotMediaFinished() nie aufgerufen.

+0

Hallo, da Ihr Worker-Thread von einer anderen API kommt, müssen Sie die Methoden dieser API verwenden. Wie auch immer, Sie haben es geschafft, von dieser Implementierung zu signalisieren. Wäre es eine Lösung, einen Zeiger auf deinen Thread in deiner App zu halten, lass Thread das Ende deines Threads signalisieren, verbinde dich mit deinem Qt MainThread und lösche es im Slot. (Wenn Sie bereits in Mainthread sind, müssen Sie nicht warten bis deleteLater). Oder, wenn Sie warten müssen, löschen Sie das Objekt nicht, sondern markieren Sie es und löschen Sie markierte Objekte wann immer Sie wollen. –

+0

Ich bin mir nicht sicher, ob ich das verstehe. Es sollte in Ordnung sein, ein Signal von einem Nicht-QThread an eine Warteschlange zu senden, solange der Zielcode * auf einem QThread ausgeführt wird. Wie erstellen Sie die Verbindung? Können Sie den Code anzeigen? –

+0

@ G.M. Die Verbindung ist Ihre übliche QObject :: connect() Variante. Nichts besonderes daran. Aber Ihr Kommentar ließ mich denken, dass die Verbindung möglicherweise in einem anderen Bibliotheks-Thread auftritt. Also habe ich Debug hinzugefügt und bestätigt: Die Verbindung wird innerhalb eines anderen (nicht Qt) Worker-Threads hergestellt. – iwarv

Antwort

1

Wenn einige der virtuellen Methoden in den Klassen von lib aufgerufen werden, werden diese aus einem Worker-Thread aufgerufen, der nicht von meiner Anwendung gestartet wurde. Dieser Thread ist kein QThread, noch könnte er jemals sein.

Es ist ein Irrglaube, dass es ein Problem ist. Es ist nicht. Der Thread, in dem Sie die Signale ausgeben, ist unwesentlich. Es muss nicht mit QThread gestartet werden.

Tatsächlich ist das Aussenden eines Signals von einem C-Callback eine idiomatische Möglichkeit, Multithread-C-Callback-APIs mit Qt zu verbinden. Es soll ohne Anstrengung funktionieren.

Ich kann von diesem Thread ausgeben, aber nur wenn ich den Steckplatz mit Qt :: DirectConnection verbinden.

Das stimmt nicht. Wenn die Signale und Steckplätze in verschiedenen Threads leben, können Sie nur direkte Verbindungen verwenden, wenn die Steckplätze/Funktoren, mit denen Sie verbunden sind, threadsicher sind. Ich bezweifle, dass Ihre sind, also sollten Sie nicht direkte Verbindung verwenden. Die automatische Verbindung wird genau das tun, was Sie brauchen.

Es funktioniert nicht, da die empfangende SipCall Instanz nicht in einem Thread mit einer laufenden Ereignisschleife lebt. Du musst es entweder in einen solchen Thread verschieben oder einen Thunk-Funktor verwenden, der im Hauptthread lebt, wie in the G.M.'s answer to this question vorgeschlagen.

Das Problem mit Thunk Functors ist, dass sie Thread-Besitz des Objekts, das Sie die Methoden aufrufen, verschleiern. Höchstwahrscheinlich sind die SipCall Methoden nicht thread-sicher entweder, also indem Sie sie vom Hauptthread aufrufen, sind Sie verpflichtet, Sachen zu brechen. Es ist am sichersten, den Player in den Anwendungsthread zu verschieben und sicherzustellen, dass er nur von diesem Thread verwendet wird. Du wirst dann keine Funktoren brauchen.

Wenn Sie sehen möchten, welche Möglichkeiten es gibt, Code (z. B. einen Funktor) in einem bestimmten Thread auszuführen, siehe this question.

+0

Danke @ Kuba-Ober, aber kannst du klären; Sie haben versehentlich ein Portmanteau zwischen den Klassen 'SipCall' und' MediaPlayer' erstellt. Welche Klasse muss in den Hauptthread verschoben werden? – iwarv

+0

Aktualisieren Sie die Seite :) In allen Fällen kümmern Sie sich um die empfangende Klasse. Das 'thread()' der Senderklasse spielt keine Rolle, da die automatische Verbindung immer den Sender-Thread zum Zeitpunkt der Signalemission prüft, * nicht * zum Zeitpunkt des Verbindungsaufbaus. Aus diesem Grund ist eine automatische Verbindung in der Regel eine gute Sache, und das Überschreiben des Verbindungstyps muss für Sonderfälle reserviert werden. Von meinem Kopf her kann ich nur an zwei Szenarien denken: Wenn Sie den Slot aus der Event-Schleife aufrufen wollen, auch wenn er im emittierenden Thread ist oder wenn Sie einen thread-sicheren Slot aufrufen. –

2

Das Problem ist, dass Ihre MediaPlayer Instanz player für einen Thread erstellt wird, der keine aktive Ereignisschleife hat.

Wenn Sie den Code zu ändern ...

ref.connect(player, SIGNAL(sigFinished()), SLOT(slotMediaFinished()), Qt::QueuedConnection); 

die Qt-Infrastruktur wird ein Ereignis an den mit player zugehörigen Thread posten. Da keine Ereignisschleife zur Verarbeitung dieses Ereignisses existiert, wird der Zielcode MediaPlayer::slotMediaFinished niemals aufgerufen.

Die Frage ist also ... welcher Thread soll MediaPlayer::slotMediaFinished angerufen werden? Wenn es der Hauptthread könnten Sie versuchen, (vorausgesetzt, Sie verwenden QT5 und C++ 11) ...

ref.connect(player, &MediaPlayer::sigFinished, QCoreApplication::instance(), 
    [&]() 
    { 
    player->slotMediaFinished(); 
    }, 
    Qt::QueuedConnection); 

, dass die QThread mit der QCoreApplication Instanz als context in der zugehörigen verwenden, um den Befehl Lambda.

Edit:

Wie Sie schon sagten Sie nicht QT5 oder C++ 11 die einzige andere Möglichkeit zu haben, schlage ich vor, eine QObject abgeleitete Variable auf Hauptanwendungsthread kann, ist verwenden können, die handeln können als Proxy-Kontext für player ...

class proxy: public QObject { 
    Q_OBJECT; 
public slots: 
    void slotMediaFinished() 
    { 
     if (MediaPlayer *player = dynamic_cast<MediaPlayer *>(sender()) { 
     player->slotMediaFinished(); 
     } 
    } 
}; 

eine Instanz der oben auf dem Anwendungs-Thread erstellen und dann connect Aussage wäre ...

ref.connect(player, SIGNAL(sigFinished()), &proxy_instance, SLOT(slotMediaFinished()), Qt::QueuedConnection) 

Wo proxy_instance die, er, proxy Instanz.Wenn nun das Signal sigFinished ausgegeben wird, wird Qt das Ereignis an proxy_instance senden. Dies wiederum ruft proxy::slotMediaFinished auf, die die MediaPlayer Instanz von Interesse von QObject::sender identifizieren und ihr slotMediaFinished Mitglied aufrufen kann.

+0

Leider sind wir hier auf der Kurve weit hinten und benutzen immer noch Qt4.7.Da wir auf andere Plattformen X-kompilieren müssen (daher Qt), können wir C++ 0x oder C++ 11 Erweiterungen (noch) nicht verwenden. – iwarv

+0

@iwarv Das bedeutet nur, dass Sie mehr Code eingeben müssen. Ein Lambda macht nichts besonderes. –

+0

Würde ich nicht von diesem Proxy erneut emittieren? Es ist die Klasse 'SipCall' mit der Methode' slotMediaFinished() ', nicht die Klasse' MediaPlayer'. Zugegebenermaßen löst die Proxy-Lösung vermutlich das 'deleteLater()' Problem (noch nicht getestet) – iwarv

Verwandte Themen