Piotr Trochim
Piotr Trochim

Reputation: 723

moving an object to a different thread

My application uses a custom serialization mechanism.

Phase 1) The mechanism loads clusters of data on a separate thread, creates all appropriate objects there and so on.

Phase 2) Once they are all fully deserialized, it hands them over to the main application thread and finishes the serialization there by for example setting up connections between objects etc.

This mechanism is a new addition to my framework – before that I was deserializing everything on the main thread.

The problem I have is that some of the objects that are created during deserializatiuon are Qt objects ( a bunch of widgets basically ). They all record the ids of a thread they were created on. And when the time comes for “phase 2”, these objects start complaing that they all don’t belong to this thread and therefore can have no signals sent etc.

So I found a method on QObject, called ‘moveToThread’. The little bugger’s not very helpful though, as it performs a check and prevents from moving objects from a different thread to the current thread ( WHY scuh a constraint, I have no clue ).

Does anyone have an idea how can I go about this? I can guarantee that the objects will only be createad on a separate thread, and from that point on they will all be living and operating on the main thread.

Thanks, Paksas

Upvotes: 5

Views: 9278

Answers (2)

Punitto Moe
Punitto Moe

Reputation: 127

Simply replace your function calls with QTimer::singleShot(int msec, const QObject *receiver, PointerToMemberFunction method) to run a slot in another thread, since according to documentation, it executes the slot from the target thread.

This way you don't need to bother with changing your code in any way, or wondering if your current object is in the same thread or not, etc.

Upvotes: 0

Pierluigi
Pierluigi

Reputation: 2294

Instances of a QObject, or any instance of subclasses of QObjects must be handled with care when it comes to multi-threading. Take a look to this page for an introduction to the topic.

Suppose that your application consists of two threads A and B. Suppose that thread A creates an object QObject instance.

instance is said to live in thread A. This means that: - it can directly send and receive signals to all other objects living in thread A. - all calls done to method of myInstance from thread A are thread-safe

Accessing instance from the other thread B, on the other hand: - is not thread safe (you must take care of race conditions). - Moreover signals emitted by instance and connected to slot of objects in thread B are not direct: the slot execution is deferred to a later moment by copying all the method parameters and placing everything in an event to be executed by thread B event queue.

Given this, the solution that you can exploit is the following.

SubClass is a subclass of a QObject.

class SubClass : public QObject{
Q_OBJECT
[...]
}

The thread A will run the following method to deserialize and populate memory with SubClass instances

void methodA(){  /this method is executed by thread A

 QThread* threadB; //a reference to the QThread related to thread B
 [...]
 MyQObject* instance = someFactoryMethod();

 //we push the ownership of instance from thrad A to thread B
 instance->moveToThread( threadB ); 

 //do something else

}

Notice however that this might not be sufficient if thread A needs to perform other operations on instance. In particular it might happens that thread A will trigger some singals defined in MyQObject. This is not allowed since now thread A is not the owner of instance anymore.

In this case you need to defer such operation and ask thread B to perform it. This is realized by using the QMetaObject::invokeLater method.

InvokeLater allows you to call a specific slot asking thread B to execute it.

Suppose that ClassInB is a class whose instances are used in thread B

class ClassInB : public QObject{
 Q_OBJECT

public:
    [...]
slots:
  void complexOperation(MyQObject* o){
    [...]
    emitSomeSignal();
  }
signals:

  void someSignal();
}

after moving instance to thread B we need to execute complexOperation() on an instance of ClassInB living in thread B which, in turn, will also emit someSignal().

void methodA(){  //this method is executed by thread A
  QThread* threadB; //a reference to the QThread related to thread B
  ClassInB* instanceOnB;   //a reference to a ClassInB instance living in thread B
  [...]

   MyQObject* instance = someFactoryMethod();

   //we push the ownership of instance from thread A to thread B
   instance->moveToThread( threadB ); 

   //we ask thread B to perform some operation related to instance
   QMetaObject::invokeLater(instanceOnB, "complexOperation", Q_ARG(MyQObject*, instance) );

}

To be able to use MyQObject* as a parameter of invokeLater we need to register it to the Meta framework. You will need to:

  • add Q_DECLARE_METATYPE(MyQObject*) in the .cpp defining MyQObject
  • call once, before using the mechanism (e.g. in the main), qRegisterMetaType<MyQObject*>();

Upvotes: 5

Related Questions