在使用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雖然不能與之相比,但經過簡單的封裝,也能寫出比較靈活或者符合自己業務需求而又簡潔好讀的異步代碼。