Search code examples
c++qtasynchronoussignalsblocking

What's the proper way to construct a signal/slot wrapper to a blocking call?


Suppose I have a QObject and a blocking method (say, it's a library call that needs to fetch a lot of data from the network before returning).

class Foo : public QObject { 
Bar* _bar;
public:
    // non blocking call, emits stuffDone when done
    void startStuff(int a, int b);
signals:
    void stuffDone(int sum);
}


class Bar {
public:
    // Blocking call
    int doStuff(int a, b) { 
        for(int i=0; i<=100000000000; i++);
        return a + b;
    }
}

I'd like my Foo::startStuff method to run doStuff in the appropriate (separate) thread and trigger a stuffDone signal upon completion. startStuff would have to return immediately.

Bar can be a QObject if necessary, thus allowing for setting thread affinity via moveToThread

What is the simplest and most idiomatic ('Qt-like') way of doing so?


Solution

  • QtConcurrent::run would probably be most idiomatic:

    struct Bar {
       // Blocks for 3 seconds
       int doStuff(int a, b) { 
          QThread::sleep(3);
          return a+b+42;
       }
    };
    
    class Foo : public QObject {
       Q_OBJECT
       Bar _bar;
    public:
       // Non-blocking, emits stuffDone when done
       void startStuff(int a, int b) {
          QtConcurrent::run([a,b,this]{
             auto result = _bar.doStuff(a,b);
             emit stuffDone(result);
          });
       }
       Q_SIGNAL void stuffDone(int sum);
    };
    

    Instead of using the custom Foo class, you could also use a QFutureWatcher, but IMHO it's more cumbersome as there's no signal that provides the result - you'd need to connect a functor that works on the result.

    QSharedPointer<Bar> bar { new Bar };
    auto watcher = new QFutureWatcher<int>;
    connect(watcher, &QFutureWatcher::finished, watcher, [watcher, bar]{
      watcher->deleteLater();
      int result = watcher->result();
      // use the result here
    });
    auto future = QtConcurrent::run(&Bar::doStuff, bar, 1, 2);
    watcher->setFuture(future);
    

    Note that the "long" addition loop is usually optimized out since it has no side effects and is thus dead code. If you want to simulate blocking, use QThread::[|m|u]sleep.