在使用Qt进行开发时,经常需要使用异步方法,不同于C#的async/await,Qt中提供了QtConcurrent::run接口方法可供调用,习惯了C#的await,便想着能不能封装几个类似的函数在项目中使用,探索了下,有如下几个方案
首先定义全局线程池:
inline QThreadPool* globalThreadPool() {static QThreadPool* pool = []() {QThreadPool* p = new QThreadPool;p->setMaxThreadCount(QThread::idealThreadCount());return p;}();return pool;}
方案一,最简单的封装调用,直接异步调用,无任何返回结果,也不会卡住调用线程:
auto CallAsync = [](auto func){QtConcurrent::run(globalThreadPool(), func);};
调用时用法如下:
CallAsync([](){// do something in theadpool...
});
方案二,如果我们想在异步执行时,调用线程同步等待,可封装如下:
auto AwaitCallAsync = [](auto func, int timeoutSeconds = 5) -> bool
{ QFuture<void> future = QtConcurrent::run(globalThreadPool(), func);// 使用智能指针管理对象生命周期auto watcher = std::make_shared<QFutureWatcher<void>>();auto loop = std::make_shared<QEventLoop>();auto timer = std::make_shared<QTimer>();bool timedOut = false; if (timeoutSeconds > 0) {timer->setInterval(timeoutSeconds * 1000);timer->setSingleShot(true);QObject::connect(timer.get(), &QTimer::timeout, [&timedOut, loop]() {timedOut = true;loop->quit();});timer->start();} QObject::connect(watcher.get(), &QFutureWatcher<void>::finished, loop.get(), &QEventLoop::quit);watcher->setFuture(future); loop->exec();// 清理资源if (!timedOut && timeoutSeconds > 0) {timer->stop();} return !timedOut; // 返回是否正常完成
};
此时,执行AwaitCallAsync时,调用线程会同步等待但并不会卡住线程,为了避免长时间等待,也可以添加超时参数。
方案三,有时,我们在希望在异步函数调用完成后能回到调用线程继续执行,那么可以添加QFutureWatcher,监控异步函数的执行,然后在QFutureWatcher发送finished时执行另一个函数,如下:
auto CallAsyncWithCallback = [](auto func_async, auto func_callback){auto future = QtConcurrent::run(globalThreadPool(), func_async);auto watcher = new QFutureWatcher<void>();// 连接信号,此处connect会被自动执行为Qt::QueuedConnectionQObject::connect(watcher, &QFutureWatcher<void>::finished, [func_callback, watcher]() mutable { func_callback();watcher->deleteLater(); // 完成后自动清理});watcher->setFuture(future);}
上面的connect是在调用线程中执行的,而finished信号是在线程池中子线程中发出来的,跨线程所以Qt会选择用Qt::QueuedConnection的方式执行Lambda 表达式。
方案四,有时,我们希望回调函数在特定线程比如主线程中执行,如下:
auto CallAsyncWithUICallback = [](FuncAsync func_async, FuncCallback func_callback_onUI) {QtConcurrent::run([func_async, func_callback]() { func_async(); // 在子线程执行异步函数// 回到主线程执行回调QMetaObject::invokeMethod(qApp, [func_callback]() {func_callback();}, Qt::QueuedConnection);});}
注意,在调用invokeMethod时,要显示指定Qt::QueuedConnection。
总体来说,C#的async await很灵活很强大,Qt虽然不能与之相比,但经过简单的封装,也能写出比较灵活或者符合自己业务需求而又简洁好读的异步代码。