К сожалению, только сигналы — и ключевое слово BlockingQueuedConnection.
А всё остальное — это как сделать синхроблок рядом с процедурой, а сигнал-слот — где-нибудь в главном окне. Скажем, так.
class AsyncSimpleContext { // интерфейс
public:
virtual void syncExec(const Runnable& body) = 0;
template <class Body>
void syncExecT(const Body& body); // тело упущу, тут всё стандартно,
// идиома «виртуальный шаблон»
}
class FmMain : public AsyncSimpleContext
{
// Разрешите не писать реализацию syncExec — тут всё просто:
// в конструкторе соединить сигнал со слотом методом BlockingQueuedConnection
// в syncExec проверить id потока и или вызвать прямо, или возбудить сигнал.
signals:
void sigSyncExec(const Runnable&);
private slots:
void slotSyncExec(const Runnable&);
}
…и передавая куда-нибудь как-нибудь интерфейс AsyncSimpleContext, мы можем синхронизировать что-то с интерфейсом.