What's the proper way to construct a signal/sl

2019-05-30 16:30发布

问题:

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?

回答1:

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.