日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

connect跨进程 qt_编写 Qt 跨线程异步调用器

發(fā)布時間:2023/12/10 编程问答 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 connect跨进程 qt_编写 Qt 跨线程异步调用器 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
本文使用 Zhihu On VSCode 創(chuàng)作并發(fā)布
本文使用 CC BY-NC-SA 4.0 許可協(xié)議,轉(zhuǎn)載請注明來源

一、設(shè)計(jì)背景

眾所周知,Qt 的信號槽系統(tǒng)提供了線程安全的跨線程異步執(zhí)行代碼的機(jī)制(Qt::QueuedConnection)。

使用該機(jī)制,可以讓槽函數(shù)代碼在另一個線程執(zhí)行,并且可以攜帶參數(shù),用戶代碼無需加鎖,只要發(fā)射信號即可。

但很多時候,我們僅僅只想單次異步執(zhí)行一段代碼。若是通過信號槽機(jī)制執(zhí)行,則就不得不聲明一個信號函數(shù),連接信號槽,再發(fā)射信號,這樣顯然很繁瑣。

幸好,Qt 本身也知道這種需求的存在,提供了 QTimer::singleShot() 函數(shù),可以跨線程異步執(zhí)行槽函數(shù),甚至還可以延遲執(zhí)行——然而該函數(shù)只能執(zhí)行無參數(shù)槽函數(shù),不能執(zhí)行其它類型的回調(diào)(如 lambda)。

所以,最好能夠有一個類似 QTimer::singleShot(),但又可以接收任意參數(shù)個數(shù)的任意函數(shù)子的 API。

更新:5.3的老代碼寫太久,思維定勢了,剛查了下5.4的 singleShot 是支持 Functor 的……那這篇文章留作該機(jī)制的技術(shù)探討吧……
更新2:Qt 5.4之后的 QTimer::singleShot 實(shí)現(xiàn)有坑,有一個 Qt 事件循環(huán)機(jī)制理論上不應(yīng)該出現(xiàn)的問題,詳見文末更新。

考慮到異步執(zhí)行時對執(zhí)行結(jié)果的訪問,可以參考 std::async(),返回一個 future 對象。但不能直接使用 std::future——因?yàn)樗?get 和 wait 會阻塞住線程,對于 Qt 而言就會阻塞事件循環(huán)。

即,我們還需要一個不會阻塞事件循環(huán)的等待機(jī)制。

綜上所述,需求總結(jié)如下:

  • 提供跨線程異步執(zhí)行代碼的能力,讓回調(diào)函數(shù)在目標(biāo)線程執(zhí)行;
  • 提供對任意函數(shù)子的異步執(zhí)行接口,可以接受具備任意參數(shù)個數(shù)的任意函數(shù)子;
  • 提供延遲執(zhí)行功能,以滿足 QTimer::singleShot() 的所有功能,便于替代前者;
  • 提供 future 返回對象,用于處理返回值和等待同步,接口與 std::future 類似;
  • 提供不阻塞 Qt 事件循環(huán)的等待機(jī)制,用于供 future 使用。
  • 二、異步回調(diào)實(shí)現(xiàn)

    跨線程異步回調(diào)的實(shí)現(xiàn),可以參考 Qt 的元對象機(jī)制。

    Qt 通過元對象系統(tǒng)進(jìn)行異步執(zhí)行時(信號槽、QTimer::singleShot()、QMetaMethod::invoke 等),本質(zhì)上是將回調(diào)函數(shù)封裝為 QMetaCallEvent 對象,再通過 QCoreApplication::postEvent() 投送至目標(biāo)對象。目標(biāo)對象會在所屬線程的事件循環(huán)中觸發(fā) QObject::event() 事件處理函數(shù),解析事件并執(zhí)行回調(diào)函數(shù)。

    然而 QMetaCallEvent 是非公開接口,Qt 不保證其接口的可用和穩(wěn)定性,因此我們需要仿照此流程自行封裝。

    2.1 異步回調(diào)事件類

    新建一個事件類,繼承自 QEvent,并注冊獲取事件類型編號:

    class AsyncInvokeEvent : public QEvent {public:static const int kEventType;std::function<QVariant(void)> Function;std::promise<QVariant>;std::shared_future<QVariant>; }; const int AsyncInvokeEvent::kEventType = QEvent::registerEventType(); AsyncInvokeEvent::AsyncInvokeEvent() : QEvent(QEvent::Type(kEventType)) {}

    將用戶通過 API 傳入的回調(diào)函數(shù)封裝為 std::function<QVariant(void)> 對象,以擦除類型信息,便于封入事件類中。

    考慮到需要獲取返回值,此處使用 Qt 的萬能動態(tài)類型 QVariant 存儲返回類型,但代價是返回值必須注冊至 Qt 元對象系統(tǒng)——也可將 future 實(shí)現(xiàn)為模板類型,但這會導(dǎo)致代碼復(fù)雜度大幅增加,并且不得不將 cpp 中的大部分流程暴露至頭文件。

    2.2 異步事件過濾器

    將異步回調(diào)事件發(fā)送至目標(biāo)線程時,需要有一個重寫了 QObject::event() 函數(shù)的對象接受該事件。我們可以考慮為每個 Qt 線程建立一個事件過濾器,使用一個全局的字典保存,在使用時通過線程指針查詢該字典,若未檢索到則新建之,即惰性初始化:

    AsyncInvokerEventFilter* filter; {// Find event filter for given threadstatic std::atomic_flag flag = ATOMIC_FLAG_INIT;static QHash<QThread*, AsyncInvokerEventFilter*> filters;while (flag.test_and_set(std::memory_order_seq_cst)) { // Spin-lock}auto it = filters.find(thread);if (it == filters.end()) {it = filters.insert(thread, new AsyncInvokerEventFilter{thread});}filter = *it;flag.clear(std::memory_order_release); }

    拿到事件過濾器后,即可向其投送事件:

    auto event = new AsyncInvokeEvent; event->Function = function; event->future = event->promise.get_future(); QCoreApplication::postEvent(filter, event); return event->future;

    該事件會通過 Qt 的事件循環(huán)機(jī)制,在目標(biāo)線程中被傳遞至接收者的 event() 函數(shù):

    bool AsyncInvokerEventFilter::event(QEvent* event) {bool ret = QObject::event(event);if (event->type() == AsyncInvokeEvent::kEventType) {AsyncInvokeEvent* e = static_cast<AsyncInvokeEvent*>(event);e->Invoke();}event->accept();return ret; }

    至此,跨線程異步執(zhí)行代碼的機(jī)制已經(jīng)編寫完畢,整體其實(shí)是非常簡單的。而且也并非 Qt 專屬,其實(shí)任意具備事件循環(huán)的框架,都可以使用相同邏輯實(shí)現(xiàn)。

    2.3 生命周期控制

    Qt 信號槽的接收者指針,除了指定槽函數(shù)執(zhí)行的線程外,還負(fù)責(zé)了生命周期控制的作用——只要 sender 或者 receiver 對象被析構(gòu),則該信號槽便不會再執(zhí)行。

    由于上文的異步回調(diào)事件類是由事件過濾器執(zhí)行,而非回調(diào)函數(shù)對應(yīng)的邏輯意義上的接收者,因此存在回調(diào)函數(shù)與其依賴資源的生命周期不一致的風(fēng)險——我們需要引入額外的信息來監(jiān)測回調(diào)函數(shù)的生命周期。

    雖然回調(diào)函數(shù)中,也可以通過各類智能指針來管理資源的生命周期,但這會強(qiáng)迫調(diào)用者編寫更多的代碼,而且無法讓事件在執(zhí)行回調(diào)前判斷相關(guān)資源生命周期是否已結(jié)束。

    因此,我們需要一個機(jī)制來判斷依賴資源的生命周期。由于在接口層可以做各式封裝,最終傳遞到執(zhí)行點(diǎn)的判斷方式,可通過 std::function<bool(void)> 來表達(dá):

    void AsyncInvokeEvent::Invoke() {QVariant ret;if (!IsAlive || IsAlive()) {ret = Function();}promise.set_value(ret); }

    對外接口中,可以考慮提供如下幾種使用方式:

    • 最基礎(chǔ)的方式,直接傳遞 std::function<bool(void)> 回調(diào)函數(shù),可在其中封裝各類自定義判斷;
    • 仿信號槽方式,傳遞 QObject* 指針,接口層通過 QPointer 類監(jiān)測其存活狀態(tài),并將其封裝為回調(diào)函數(shù);
    • 無生命周期約束,則接口層封裝默認(rèn)實(shí)現(xiàn)的回調(diào)函數(shù),自動返回 true。

    三、異步回調(diào)接口封裝

    根據(jù)上文代碼,此機(jī)制的接口需要提供 (執(zhí)行線程, 回調(diào)函數(shù)) 二元組作為輸入?yún)?shù),以及一個可選參數(shù) [生命周期判斷回調(diào)]。

    為方便使用,參考 Qt 的信號槽、 QTimer::singleShot() 語法,也可直接提供一個 QObject* 對象指針作為邏輯意義上的接收者,則可通過 QObject::thread() 函數(shù)獲取執(zhí)行線程。

    回調(diào)函數(shù)最終傳遞至內(nèi)部實(shí)現(xiàn)的版本,便是上文所述的 std::function<QVariant(void)> 對象。但為方便使用,我們可以提供 Func function, Args&&... args 形式的模板接口,用于承接任意類型的函數(shù)子和函數(shù)參數(shù):

    template <typename Func, typename... Args> AsyncInvoker::Future AsyncInvoker::Invoke(QThread* thread, const Func& func,Args&&... args) {if (!thread) {thread = qApp->thread();}auto f = std::bind(func, std::forward<Args>(args)...);std::function<QVariant(void)> function = [f]{ return QVariant{f()}; };return Invoke(function, thread); }

    此處的封裝返回值一句存在隱患,因?yàn)閭魅牒瘮?shù)有可能無返回值,此時這行代碼會無法編譯。

    針對此情況,我們可以去 Qt 源碼中看看官方是如何處理的。順著接收函數(shù)子作為槽函數(shù)的 QObject::connect() 源代碼,可在 qobjectdefs_impl.h 中找到如下黑魔法:

    /*trick to set the return value of a slot that works even if the signal or the slot returns voidto be used like function(), ApplyReturnValue<ReturnType>(&return_value)if function() returns a value, the operator,(T, ApplyReturnValue<ReturnType>) is called, but if itreturns void, the builtin one is used without an error. */ template <typename T> struct ApplyReturnValue {void *data;explicit ApplyReturnValue(void *data_) : data(data_) {} }; template<typename T, typename U> void operator,(T &&value, const ApplyReturnValue<U> &container) {if (container.data)*reinterpret_cast<U *>(container.data) = std::forward<T>(value);}template<typename T> void operator,(T, const ApplyReturnValue<void> &) {}

    該模板類重載了逗號運(yùn)算符,然后再通過模板特化匹配到不同版本的實(shí)現(xiàn),對于有返回值的版本,將返回值儲存至構(gòu)造時輸入的對象指針中。

    仿寫一下,就能得到我們想要的了:

    namespace impl { template <typename T> struct ApplyReturnValue {mutable QVariant* data_;explicit ApplyReturnValue(QVariant* data) : data_(data) {} }; template <typename T, typename U> inline void operator,(T&& value, const ApplyReturnValue<U>& container) {container.data_->setValue(std::forward<T>(value)); } template <typename T> inline void operator,(T, const ApplyReturnValue<void>&) {} } // namespace impltemplate <typename Func, typename... Args> AsyncInvoker::Future AsyncInvoker::Invoke(QThread* thread, const Func& func,Args&&... args) {if (!thread) {thread = qApp->thread();}auto f = std::bind(func, std::forward<Args>(args)...);std::function<QVariant(void)> function = [f] {using return_t = decltype(func(std::forward<Args>(args)...));QVariant ret;f(), impl::ApplyReturnValue<return_t>(&ret);return ret;};return Invoke(function, thread); }

    注意:lambda 的返回類型無法通過 std::result_of 獲取,只能通過 decltype 獲取。

    四、延遲執(zhí)行

    延遲執(zhí)行原理上也很簡單,將延遲事件一并封裝入異步回調(diào)事件類中,投送至事件過濾器后,事件過濾器再啟動一個定時器事件,在定時器事件中才實(shí)際執(zhí)行回調(diào)。

    考慮到性能問題,此處不應(yīng)為了執(zhí)行一個回調(diào)函數(shù)就創(chuàng)建一個 QTimer 定時器對象,并綁定信號槽。

    好消息是,Qt 已經(jīng)考慮到此類需求,提供了一個輕量級的定時器接口 QObject::startTimer(),無需額外新建任何對象以及信號槽。該接口會定時發(fā)起定時器事件,通過 QObject::timerEvent 接收處理。

    因此,將前文的 AsyncInvokerEventFilter::event() 代碼進(jìn)行改造如下:

    // AsyncInvokeEvent 成員變量: // QSharedPointer<AsyncInvokeData> d;// AsyncInvokerEventFilter 成員變量: // QHash<int, QSharedPointer<AsyncInvokeData>> events_;bool AsyncInvokerEventFilter::event(QEvent* event) {bool ret = QObject::event(event);if (event->type() == AsyncInvokeEvent::kEventType) {AsyncInvokeEvent* e = static_cast<AsyncInvokeEvent*>(event);if (e->d->delay_ms > 0) {// Deferred event, invoke in timerEventint id = startTimer(e->d->delay_ms);events_[id] = e->d;} else {e->d->Invoke();}}event->accept();return ret; }void AsyncInvokerEventFilter::timerEvent(QTimerEvent* event) {int id = event->timerId();killTimer(id);auto it = events_.find(id);if (it == events_.end()) {return;}it.value()->Invoke();events_.erase(it); }

    注意

    對于自定義事件,無論 QObject::event() 返回是 true 還是 false,或者通過 QEvent::accept() / QEvent::ignore() 接受或者忽略事件,Qt 都會無視上述操作,在執(zhí)行完 QObject::event() 后,直接刪除由 QCoreApplication::postEvent() 投送的異步事件對象。

    因此,對于需要延遲執(zhí)行的事件,直接將事件指針保存下來是無效的,該指針會成為懸空指針。

    此處使用共享指針保存事件數(shù)據(jù),而非直接與容器內(nèi)的值進(jìn)行 std::swap()——因?yàn)檫@些數(shù)據(jù)在 Future 中也會被引用,需要進(jìn)行共享。

    此處不可使用 QCoreApplication::processEvents() 方式進(jìn)行延時——因?yàn)槿粼谘訒r過程中又接收到異步回調(diào)事件,則會遞歸進(jìn)入此函數(shù),以此類推,存在多次遞歸導(dǎo)致爆棧的風(fēng)險。

    五、Future 對象

    其實(shí),簡單一點(diǎn)的話,在異步回調(diào)事件類中存儲一個 std::promise 對象,然后返回它的 get_future() 即可。

    但前文也提到了,std::future 等待操作會阻塞線程,導(dǎo)致 Qt 事件循環(huán)失去響應(yīng),因此我們需要編寫一個不阻塞 Qt 事件循環(huán)的等待機(jī)制,并且基于它來封裝我們的 Future 類。

    5.1 不阻塞 Qt 事件循環(huán)的等待

    這個等待機(jī)制,想必很多人都已經(jīng)在自己的項(xiàng)目中廣泛應(yīng)用,即使用計(jì)時器配合 QCoreApplication::processEvents() 實(shí)現(xiàn)不阻塞事件循環(huán)的延時:

    QElapsedTimer timer; timer.start() while (timer.elapsed() < timeout) {QCoreApplication::processEvents(); }

    為方便定制化的使用,我們可以參考 std::future 的 wait() / wait_for() / wait_until() 函數(shù),做多個額外的封裝,并提供 QDateTime 和 std::chrono 兩套接口:

    void Wait(const std::function<bool(void)>& isValid,QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents);bool WaitFor(int timeout_milliseconds, QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents,const std::function<bool(void)>& isValid = {});bool WaitUntil(const QDateTime& timeout_time,QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents,const std::function<bool(void)>& isValid = {});template <class Rep, class Period> bool WaitFor(const std::chrono::duration<Rep, Period>& timeout_duration,QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents,const std::function<bool(void)>& isValid = {});template <class Clock, class Duration> bool WaitUntil(const std::chrono::time_point<Clock, Duration>& timeout_time,QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents,const std::function<bool(void)>& isValid = {});

    具體實(shí)現(xiàn)不再贅述,本例思路如下:

    • WaitFor 中,使用 當(dāng)前時間 + 延時 方式轉(zhuǎn)換為 WaitUntil 的調(diào)用。
    • WaitUntil 中,將超時判斷封裝為回調(diào)函數(shù),以轉(zhuǎn)換為 Wait 的調(diào)用。

    5.2 Future 對象的 wait 與 get

    Future 對象的 wait()/wait_for()/wait_until() 可直接調(diào)用上述實(shí)現(xiàn)。

    但 wait_for() / wait_until() 函數(shù)需要返回 std::future_status 狀態(tài)值,因此我們還需要判斷該異步事件當(dāng)前的執(zhí)行狀態(tài)。

    想必由于要避免阻塞事件循環(huán),我們不能直接調(diào)用 std::future 的對應(yīng)函數(shù),因此需要自行封裝執(zhí)行狀態(tài)。

    可考慮在異步回調(diào)事件類對象中存儲一個 std::atomic_bool 標(biāo)志位,用于標(biāo)識異步執(zhí)行狀態(tài),在回調(diào)執(zhí)行后將其之為 true:

    // Future 成員變量: // QSharedPointer<AsyncInvokeData> d_;std::future_status AsyncInvoker::Future::status() const {if (!d_->future.valid()) {return std::future_status::deferred;} else if (!d_->executed.load()) {return std::future_status::timeout;} else {return std::future_status::ready;} }

    則 wait_for() 和 wait_until() 函數(shù)在完成等待后,返回 status() 即可;wait() 則是將 status() 作為判斷條件傳給上一節(jié)的 Wait() 函數(shù)。

    get() 函數(shù)同理, `status()` 可以直接使用 wait() 完成等待,然后返回 std::future::get() 即可。

    valid() 函數(shù)則是同時判斷 std::future::valid() 和 executed 狀態(tài),即 status() == std::future_status::ready。

    六、范例代碼

    上文中的代碼,已提交至 GitHub: ZgblKylin/KtUtils 倉庫的 AsyncInvoker 分支。

    該倉庫提供 CMake 和 QMake 兩種使用方式,支持靜態(tài)鏈接和動態(tài)鏈接(QMake 還提供源碼包含)。

    庫文件會生成至 ${CMAKE_SOURCE_DIR}/lib 目錄,dll文件(特例)和單元測試的exe文件會生成至 ${CMAKE_SOURCE_DIR}/bin 目錄,庫文件名稱為 KtUtils/KtUtilsd(Debug 后綴)。

    CMake 使用方式

    # 啟用動態(tài)鏈接。默認(rèn)使用靜態(tài)鏈接。 set(KT_UTILS_SHARED_LIBRARY ON)# 編譯單元測試 set(BUILD_TESTING ON)# 鏈接目標(biāo) add_subdirectory(KtUtils) target_link_libraries(TargetName KtUtils)

    單元測試使用 Qt Test 編寫,可使用 CMake 的 CTest 機(jī)制直接執(zhí)行(如 make test),但該執(zhí)行方式下無法看到 Qt Test 輸出。

    QMake 使用方式

    # 源碼包含 include(KtUtils/KtUtils.pri)# 鏈接庫 # 修改 KtUtilsconf.pri 以啟用動態(tài)鏈接、啟用單元測試 SUBDIRS += KtUtils win32: {contains(KtUtils_CONFIG, KtUtils_Shared_Library) {LIBS += -LKtUtils/bin/} else {LIBS += -LKtUtils/lib/} } else:unix: {LIBS += -LKtUtils/lib/ } CONFIG(release, debug|release): LIBS += -lKtUtils else:CONFIG(debug, debug|release): LIBS += -lKtUtilsd DESTDIR = KtUtils/bin INCLUDEPATH += KtUtils/include

    七、QTimer::singleShot

    7.1 功能對比

    筆者之前寫了5年的 Qt 5.3,所以形成了一定的思維定勢,加上 Qt 極端注重兼容性,基本不在大版本內(nèi)做大更新,所以忽略了某些問題……

    就是 Qt 5.4 其實(shí)算 breaking change,只是不破壞老代碼兼容性。5.4 開始,API 設(shè)計(jì)全面提升到 C++11了,于是很多 API 都引入了 Functor 版本。

    5.4 的 QTimer::singleShot 加入了 Functor+Args 的接口,接口設(shè)計(jì)和功能與我文中的幾乎一致。

    但我試用了,發(fā)現(xiàn)有一個坑——無法在非 Qt 線程中調(diào)用 QTimer::singleShot,此場景下該函數(shù)不會被執(zhí)行。

    但 Qt 的事件循環(huán)機(jī)制是不應(yīng)該有這問題的,因?yàn)?Qt 的異步事件的處理(底層為QCoreApplication::postEvent)只取決于接收者的事件循環(huán),對發(fā)送者無任何要求。典型例子就是信號槽,你可以在任何位置發(fā)信號,甚至在類似中斷的 catch 塊、signal 函數(shù)回調(diào)等這些特殊位置發(fā)信號。

    那么 QTimer::singleShot 的這個問題是怎么出現(xiàn)的呢?這需要我們對比下兩個方案的實(shí)現(xiàn)方式。

    7.2 問題分析

    我的方案:

    • 人工仿造 QMetaCallEvent;
    • 通過 QCoreApplication::postEvent 投遞事件;
    • receiver 接收事件后,再根據(jù) timeout 參數(shù)來決定是否需要延時,若需要,則再通過 startTimer 轉(zhuǎn)發(fā)至 timerEvent 事件。

    QTimer::singleShot 的方案:

    該方案比較取巧,把 invoke 和 timeout 兩個動作合并到一起了,然后比起我的方案還不需要給接收線程外掛一個 filter 處理器,整體實(shí)現(xiàn)上的確更加優(yōu)雅,但也導(dǎo)致了此處的問題。

    • 建立一個 `QSingleShotTimer` 對象,該對象本身承擔(dān)了 invoke 功能,同時繼承自 QObject,來一并處理延時功能;
    • 直接在調(diào)用線程對該對象執(zhí)行 startTimer 操作——因?yàn)榇瞬僮鞑荒芸缇€程調(diào)用;
    • 通過 moveToThread 將其移入接收者線程,則已經(jīng)啟動的定時器會在該線程自動重新開啟;
    • 不用管了,也不需要做啥 post,把調(diào)用請求投送到另一個線程,以及延遲執(zhí)行,都通過 moveToThread 這步一石二鳥了;
    • 在 timerEvent 中直接 invoke 函數(shù)即可,多么優(yōu)雅。

    唯一紕漏在于,非 Qt 線程(無 Qt 事件循環(huán)的線程)中無法啟動定時器!

    此時, moveToThread 做的“停止原線程中的定時器,移動對象所有權(quán)到新線程后,在新線程中自動注冊定時器”的自動操作,一開始就被堵死了。

    于是這個定時器永遠(yuǎn)跑不起來,這個函數(shù)永遠(yuǎn)不會被執(zhí)行。

    對了,順帶還引發(fā)一個額外的副作用——如果你這個 functor 是捕獲了變量的 lambda,那么捕獲的變量也就釋放不掉了——也不是嚴(yán)格意義上的野指針化了,因?yàn)樵谶M(jìn)程退出前,還是會析構(gòu)掉這個 QSingleShotTimer 對象的。

    7.3 替代方案

    那么,為了避開這個坑,難道我們就一定要重復(fù)造輪子了嗎?

    也不是,Qt 還是有一個老老實(shí)實(shí)走 QCoreApplication::postEvent 投遞 QMetaCallEvent 的實(shí)現(xiàn)的。

    那就是 QMetaObject::invokeMethod。

    只是延遲執(zhí)行功能就得自己造輪子了:

    QMetaObject::invokeMethod(receiver, [timeout]{// 以下延時也可通過我前文封裝的 WaitFor 函數(shù)實(shí)現(xiàn)auto start = std::chrono::steady_clock::now();std::chrono::milliseconds duration{timeout};while (std::chrono::steady_clock::now() < (start + duration)) {QCoreApplication::processEvents();}...}, Qt::QueuedConnection);

    怎么說呢?放著 QMetaCallEvent 的正道不走,非要為了優(yōu)雅玩花活,結(jié)果玩出了一個本不應(yīng)該有的坑……

    建議有異步延遲執(zhí)行的需求時,老老實(shí)實(shí)走最正統(tǒng)的 QMetaObject::invokeMethod 吧,無非是封裝個 WaitFor 方法,多寫一行代碼來延時罷了。

    總結(jié)

    以上是生活随笔為你收集整理的connect跨进程 qt_编写 Qt 跨线程异步调用器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。