2

The story :

I make use of the QtConcurrent API for every "long" operation in my application. It works pretty well, but I face some problems with the QObjects creation.

Consider this piece of code, which use a thread to create a "Foo" object :

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

It works well, but if the "Foo" class is derived from QObject class, the "result" instance belongs to the QThread who has created the object.

So to use properly signal/slot with the "result" instance, one should do something like this :

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

Now, all works as exepected, and I think this is the normal behaviour and the nominal solution.

The problem :

I have a lot of this kind of code, which sometimes create objects, which can also create objects. Most of them are created properly with a "moveToThread" call.

But sometimes, I miss one "moveToThread" call.

And then, a lot of things look like they doesn't work (because this object slots are "broken"), without any Qt warning.

Now, I sometimes spend a lot of time to figure why someting doesn't work, before understanding it's only because the slots are no more called on a particular object instance.

The question :

Is there any way to help me to prevent/detect/debug this kind of situation ? For example :

  • having a warning logged every time a QThread is deleted but there are objects alive who belongs to it ?
  • having a warning logged every time a signal is emitted to an object which QThread is deleted ?
  • having a warning logged every time a signal is emitted to an object (in another thread) and not processed before a timeout ?

Thanks

Aurelien
  • 1,032
  • 2
  • 10
  • 24
  • Is there a reason that the `Foo` type `QObject`s have to have non-main thread affinity (what I think you mean by "belongs to")? – eclarkso Jul 27 '16 at 18:04
  • The third question is a duplicate; please remove it. You are asking how to detect hung event loop - this is independent of whether there's a signal being emitted. See [here](http://stackoverflow.com/q/25038829/1329652). – Kuba hasn't forgotten Monica Jul 28 '16 at 14:21

1 Answers1

2

It is possible to track an object's movement among threads. Just before an object is moved to the new thread, it is sent a ThreadChange event. You can filter that event and have your code run to take a note of when an object leaves a thread. But it's too early at that point to know of whether the object goes anywhere. To detect that, you need to post a metacall (see this question) to the object's queue to be executed as soon as the object's event processing resumes in the new thread. You'd also attach to QThread::finished to get a chance to look through your object list and check if any of them live on the thread that's about to die.

But all this is fairly involved: each thread will need its own tracker/filter object, as event filters must live in the object's thread. We're probably talking of more than 200 lines of code to do it right, handling all corner cases.

Instead, you can leverage RAII and hold your objects using handles that manage thread affinity as a resource (because it is one!):

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

You can return a MainResult by value, but the return type of the functor must be explicitly given:

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

Alternatively, you can return the result of calling MainResult; it's a functor itself to save a bit of typing but this might be considered a hack and perhaps you should convert operator()() to a method with a short name.

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

While it's preferable to construct the object along with the handle, it's also possible to pass an instance pointer to the handle's constructor:

MainResult<Foo> obj{ new Foo{1} };
Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313