wx.CallAfter 机制分析

1. 基本概念

wx.CallAfter 是 wxWidgets 框架提供的一个线程安全的方法调用机制,允许从任何线程(包括工作线程)安全地调用 GUI 对象的方法。它通过事件循环机制确保方法在主线程中执行,从而避免了多线程环境下直接操作 GUI 组件可能引发的线程安全问题。

2. 主要用途

  • 线程安全的 GUI 操作:从工作线程安全地更新 GUI 界面
  • 延迟执行:将方法调用推迟到事件循环的下一个空闲周期执行
  • 异步方法调用:实现非阻塞的方法调用机制

3. 底层实现机制

3.1 核心类结构

wx.CallAfter 的实现基于以下几个核心类:

  1. wxAsyncMethodCallEvent:抽象基类,定义了异步方法调用事件的基本结构
  2. wxAsyncMethodCallEvent0/1/2:模板类,分别处理 0、1、2 个参数的方法调用
  3. wxAsyncMethodCallEventFunctor:模板类,处理函数对象的调用
  4. wxEvtHandler::CallAfter:模板方法,用于创建并队列化异步方法调用事件

3.2 实现流程

// wxAsyncMethodCallEvent 基类
class wxAsyncMethodCallEvent : public wxEvent
{
public:
    virtual void Execute() = 0;
};

// 处理无参数方法调用的模板类
template <typename T>
class wxAsyncMethodCallEvent0 : public wxAsyncMethodCallEvent
{
public:
    virtual void Execute() override
    {
        (m_object->*m_method)();
    }
};

// 处理带参数方法调用的模板类
template <typename T, typename T1>
class wxAsyncMethodCallEvent1 : public wxAsyncMethodCallEvent
{
public:
    virtual void Execute() override
    {
        (m_object->*m_method)(m_param1);
    }
};

3.3 调用流程

  1. 事件创建CallAfter 方法根据调用的参数数量创建相应的 wxAsyncMethodCallEvent 对象
  2. 事件队列:通过 QueueEvent 方法将事件添加到事件处理队列中
  3. 事件循环唤醒:调用 wxWakeUpIdle() 唤醒事件循环
  4. 事件处理:在事件循环中处理队列中的事件,调用 Execute() 方法执行实际的方法调用

3.4 平台特定的唤醒机制

Windows 平台
void wxMSWEventLoopBase::WakeUp()
{
    // 通过设置事件对象唤醒事件循环
    if ( !::SetEvent(m_heventWake) )
        wxLogLastError(wxS("SetEvent(wake)"));
}
GTK 平台
void wxApp::WakeUpIdle()
{
#if wxUSE_THREADS
    wxMutexLocker lock(m_idleMutex);
#endif
    // 通过添加空闲回调函数唤醒事件循环
    if (m_idleSourceId == 0)
        m_idleSourceId = g_idle_add_full(G_PRIORITY_LOW, wxapp_idle_callback, nullptr, nullptr);
}
Unix 平台
void wxWakeUpPipe::WakeUpNoLock()
{
    // 向管道写入数据以唤醒事件循环
    if ( write(m_pipe[wxPipe::Write], "s", 1) != 1 )
    {
        perror("write(wake up pipe)");
    }
    else
    {
        m_pipeIsEmpty = false;
    }
}

4. 时序图

工作线程事件队列事件循环GUI对象CallAfter(method, args...)创建wxAsyncMethodCallEvent并加入队列wxWakeUpIdle()唤醒事件循环ProcessPendingEvents()处理队列中的事件event->>Execute()在主线程中执行方法调用工作线程事件队列事件循环GUI对象

5. 使用示例

// 从工作线程安全地更新GUI
class MyFrame : public wxFrame
{
public:
    void UpdateStatus(const wxString& message)
    {
        m_statusBar->SetStatusText(message);
    }
    
    void StartBackgroundTask()
    {
        // 在工作线程中启动耗时操作
        std::thread worker([this]() {
            // 执行耗时操作
            DoWork();
            
            // 完成后安全地更新GUI
            wxTheApp->GetTopWindow()->CallAfter(&MyFrame::UpdateStatus, 
                                               wxString("任务完成"));
        });
        worker.detach();
    }
};

6. 注意事项

  1. 线程安全CallAfter 本身就是线程安全的,可以在任何线程中调用
  2. 对象生命周期:确保调用 CallAfter 的对象在方法实际执行时仍然有效
  3. 性能考虑:避免频繁调用 CallAfter,以免事件队列积压
  4. 参数传递:传递给 CallAfter 的参数会被复制,需要注意参数的拷贝语义

7. 总结

wx.CallAfter 机制通过事件循环和队列化处理的方式,提供了一种安全、高效的跨线程方法调用解决方案。它隐藏了线程同步的复杂性,让开发者能够专注于业务逻辑的实现,是 wxWidgets 框架中处理多线程 GUI 应用的重要工具。

// ====== 改进的环境修复代码 ====== (function() { // 1. 检测是否在原生平台 if (cc.sys.isNative) { try { console.log('Applying environment fix for', cc.sys.os); // 2. 获取全局对象的更可靠方法 const getGlobalObject = () => { // 尝试 Cocos Creator 的全局对象 if (typeof cc !== 'undefined' && cc.game && cc.game.globalObject) { return cc.game.globalObject; } // Node.js 环境 if (typeof global !== 'undefined') return global; // 浏览器环境 if (typeof window !== 'undefined') return window; // Web Worker 环境 if (typeof self !== 'undefined') return self; // 终极回退 return Function('return this')() || {}; }; const globalObj = getGlobalObject(); // 3. 修复关键全局变量 if (typeof global === 'undefined') { global = globalObj; console.log('Fixed global'); } if (typeof self === 'undefined') { self = globalObj; console.log('Fixed self'); } // 4. 确保全局对象链完整 if (typeof globalThis === 'undefined') { globalThis = globalObj; } // 5. 验证修复结果 console.log('Environment verification:'); console.log('typeof global:', typeof global); console.log('typeof self:', typeof self); console.log('self === global:', self === global); // 6. 特殊修复:确保全局对象上有 self 属性 if (globalObj && typeof globalObj.self === 'undefined') { Object.defineProperty(globalObj, 'self', { value: globalObj, writable: false, configurable: false, enumerable: false }); console.log('Added self to global object'); } } catch (e) { console.error('Environment fix failed:', e); } } else { console.log('Skipping environment fix for non-native platform'); } })(); // ====== 环境修复结束 ====== // 在环境修复后立即验证 setTimeout(() => { console.log('Environment verification after fix:'); console.log('self defined:', typeof self !== 'undefined'); console.log('global defined:', typeof global !== 'undefined'); console.log('self === global:', self === global); console.log('globalThis === self:', globalThis === self); // 尝试访问典型全局属性 try { console.log('self.setTimeout test:', typeof self.setTimeout); } catch (e) { console.error('Global access failed:', e); } }, 100); // 兼容 JSB 环境:确保 self 存在 if (typeof self === 'undefined') { self = globalThis || global || window || {}; console.warn('Fallback self initialization'); } cc.game.onStart = function() { // 1. 初始化游戏设置 cc.view.enableRetina(true); cc.view.resizeWithBrowserSize(true); // 2. 调整设计分辨率和适配策略 cc.view.setDesignResolutionSize(960, 640, cc.ResolutionPolicy.SHOW_ALL); // 3. 加载重力引擎 SDK(核心位置) loadGravityEngineSDK(); // 4. 加载启动场景 cc.assetManager.loadBundle('main', (err) => { if (err) return console.error('Bundle load failed:', err); cc.director.loadScene('start'); }); }; function loadGravityEngineSDK() { const primaryPath = 'src/assets/_plugs/lib/gravityengine.mg.cocoscreator.min.js'; const fallbackPath = 'assets/_plugs/lib/gravityengine.mg.cocoscreator.min.js'; cc.assetManager.loadScript(primaryPath, (err) => { if (err) { console.error('Failed to load gravityengine SDK (primary):', err); // 尝试备用路径 console.log('Trying fallback path:', fallbackPath); cc.assetManager.loadScript(fallbackPath, (altErr) => { if (altErr) { console.error('Fallback load failed:', altErr); handleSDKLoadFailure(); } else { console.log('GravityEngine SDK loaded from fallback'); initGravityEngine(); } }); } else { console.log('GravityEngine SDK loaded successfully'); initGravityEngine(); } }); } function initGravityEngine() { // 确保 SDK 已加载 if (typeof GravityEngine === 'undefined') { console.error('GravityEngine SDK not available after loading!'); return; } // 初始化 SDK GravityEngine.init({ appId: 'YOUR_APP_ID', enableLog: true }); console.log('GravityEngine initialized'); } function handleSDKLoadFailure() { // 降级处理:禁用相关功能或显示提示 console.warn('SDK load failed, disabling analytics features'); window.trackEvent = () => {}; // 空函数降级 } cc.game.run(); var i; Object.defineProperty(exports, "__esModule", { value: true }); var r_Const_Common = require("Const_Common"); var r_GravityPlatform = require("GravityPlatform"); var r_YpNetMag = require("YpNetMag"); var r_EvenType = require("EvenType"); var r_PlayerDataManager = require("PlayerDataManager"); var r_ExcelLoader = require("ExcelLoader"); var r_GameDataManager = require("GameDataManager"); var r_GameGlobalVariable = require("GameGlobalVariable"); var r_UIConfig_Game = require("UIConfig_Game"); var r_UIConfig_Home = require("UIConfig_Home"); var r_BundleConfig = require("BundleConfig"); var r_GameConfig = require("GameConfig"); var r_AudioManager = require("AudioManager"); var r_EventManager = require("EventManager"); var r_HttpManager = require("HttpManager"); var r_LogManager = require("LogManager"); var r_ResLoader = require("ResLoader"); var r_UIManager = require("UIManager"); var r_UIView = require("UIView"); var _decorator = cc._decorator; var _ccclass = _decorator.ccclass; var _property = _decorator.property; var def_UI_Entry = function (e) { function _ctor() { var t = null !== e && e.apply(this, arguments) || this; t.progress_loading = null; return t; } __extends(_ctor, e); _ctor.prototype.onLoad = function () { cc.director.getCollisionManager().enabled = true; }; _ctor.prototype._show = function () { return __awaiter(this, undefined, Promise, function () { var e; var t = this; return __generator(this, function () { this.progress_loading.progress = 0; if (cc.sys.isBrowser) { YPSDK.init(39, "https://platform-shop-dev.hanyougame.com", { gameChannelList: { h5: { platformType: "h5", describe: "默认 H5 渠道", version: "1.0.0" }, tt: { platformType: "tt", appId: "tt09297f94961f881b02", describe: "默认 TT 渠道", version: "1.0.0" }, wx: { platformType: "wx", appId: "wx6baaaa27ab5186ff", describe: "默认 WX 渠道", version: "1.0.0" } } }); YPSDK.login(); } e = cc.tween(this).delay(10).call(function () { console.error("10s后,仍然未登录成功,直接进入游戏"); t.startLoadGame(); }).start(); YPSDK.setLoginCallBack(function (o) { return __awaiter(t, undefined, undefined, function () { return __generator(this, function () { if (o) { r_GravityPlatform.default.GA_Init(YPSDK.Platform.loginData.bindingId); e.stop(); this.startLoadGame(); } return [2]; }); }); }); return [2]; }); }); }; _ctor.prototype.startLoadGame = function () { var e = []; e.push(this._loadGameConfig); e.push(this._loadRemoteConfig); e.push(this._loadExcelData); e.push(this._loadUserData); e.push(this._loadCommonBundle); e.push(this._loadMainBundle); this._completeTasks(e, this._loadGame); cc.sys.platform == cc.sys.WECHAT_GAME && mg.showShareMenu({ menus: ["shareAppMessage", "shareTimeline"] }); }; _ctor.prototype._loadExcelData = function () { return new Promise(function (e) { var t = new Date().getTime(); r_ExcelLoader.ExcelLoader.loadAll().then(function () { console.log("游戏数据表加载成功", new Date().getTime() - t); e(true); }); }); }; _ctor.prototype._loadGame = function () { cc.sys.platform == cc.sys.WECHAT_GAME && r_LogManager.LogMgr.info("开始游戏!" + mg.getChannel()); this._close(); if (r_PlayerDataManager.PlayerDataMgr.GetGuideIndexByTaskName(r_Const_Common.GuideName.战斗背包) != r_Const_Common.GameBagGuideIndex.引导完结) { r_PlayerDataManager.PlayerDataMgr.GMSetGuideIndex(r_Const_Common.GuideName.战斗背包, r_Const_Common.GameBagGuideIndex.引导初始1); r_GameDataManager.GameDataMgr.ClearGameBagData(); r_GameGlobalVariable.GameGlobalVariable.nowlevel = 1; r_UIManager.default.open(r_BundleConfig.BundleNames.Game, r_UIConfig_Game.UIView_Game.UI_GameView); } else { r_EventManager.EventMgr.dispatchEvent(r_EvenType.EVENT_TYPE.Game_Load_View, true); r_UIManager.default.open(r_BundleConfig.BundleNames.Home, r_UIConfig_Home.UIView_Home.UI_Hall); } this.onInitWhiteName(); }; _ctor.prototype._completeTasks = function (e, t) { var o = this; var n = 0; var i = 0; var a = Promise.resolve(); e.forEach(function (t) { a = a.then(function () { return t(); }).then(function () { n++; o._setProgress(n / e.length); }).catch(function () { i++; }); }); return a.then(function () { 0 == i && t.call(o); }); }; _ctor.prototype._loadGameConfig = function () { var e = this; return new Promise(function (t, o) { return __awaiter(e, undefined, undefined, function () { var e; var n; return __generator(this, function (i) { switch (i.label) { case 0: e = new Date().getTime(); return [4, r_ResLoader.default.loadRes("resources", "config/GameConfig", cc.JsonAsset)]; case 1: if (n = i.sent()) { r_GameConfig.default.appConfig = n.json; console.log("Load game config success:", r_GameConfig.default.appConfig); n.decRef(); t(true); console.log("游戏本地配置加载成功", new Date().getTime() - e); } else { console.log("Load game config failure"); o(false); } return [2]; } }); }); }); }; _ctor.prototype._loadRemoteConfig = function () { var e = this; return new Promise(function (t, o) { return __awaiter(e, undefined, undefined, function () { var e; var n; var i; var a; return __generator(this, function (r) { switch (r.label) { case 0: e = new Date().getTime(); if (n = r_GameConfig.default.appConfig.RemoteUrl) { i = cc.path.join(n, "ADConfig.json"); return [4, r_ResLoader.default.loadRemote(i, { ext: ".json" })]; } else { return [3, 2]; } case 1: if ((a = r.sent()) && a.json) { r_GravityPlatform.default.videoId = a.json.ADUnitId[0]; r_GameConfig.default.adConfig = a.json; console.log("Load remote config success:", r_GameConfig.default.adConfig); t(true); console.log("远程配置加载成功", new Date().getTime() - e); } else { console.log("Load remote config failure:", i); o(false); } return [3, 3]; case 2: console.log("Load remote config failure: RemoteUrl is null"); o(false); r.label = 3; case 3: return [2]; } }); }); }); }; _ctor.prototype._loadCommonBundle = function () { var e = this; return new Promise(function (t, o) { return __awaiter(e, undefined, undefined, function () { var e; return __generator(this, function () { e = new Date().getTime(); r_ResLoader.default.loadBundle(r_BundleConfig.BundleNames.Common).then(function (n) { console.log("Loaded main bundle", n); if (n) { t(true); console.log("Common分包下载", new Date().getTime() - e); } else { o(false); } }); return [2]; }); }); }); }; _ctor.prototype._loadMainBundle = function () { var e = this; return new Promise(function (t, o) { return __awaiter(e, undefined, undefined, function () { var e; return __generator(this, function () { e = new Date().getTime(); r_ResLoader.default.loadBundle(r_BundleConfig.MainGameBundle).then(function (n) { console.log("Loaded main bundle", n); if (n) { t(true); console.log("Home分包下载", new Date().getTime() - e); } else { o(false); } }); return [2]; }); }); }); }; _ctor.prototype._yp_sdk_init = function () { var e = this; var t = r_GameConfig.default.appConfig.Version; var o = { gameChannelList: { h5: { platformType: "h5", describe: "默认 H5 渠道", version: t }, tt: { platformType: "tt", appId: "tt09297f94961f881b02", describe: "默认 TT 渠道", version: t }, wx: { platformType: "wx", appId: "wx6baaaa27ab5186ff", describe: "默认 WX 渠道", version: t } } }; return new Promise(function (t, n) { return __awaiter(e, undefined, undefined, function () { var e; return __generator(this, function () { e = new Date().getTime(); YPSDK.init(r_GameConfig.default.appConfig.ServerID, r_GameConfig.default.appConfig.ServerUrl, o).then(function () { t(true); console.log("游品服务器初始化", new Date().getTime() - e); }).catch(function () { n(false); }); return [2]; }); }); }); }; _ctor.prototype._yp_sdk_login = function () { var e = this; return new Promise(function (t, o) { return __awaiter(e, undefined, undefined, function () { return __generator(this, function () { YPSDK.login().then(function () { t(true); }).catch(function () { o(false); }); return [2]; }); }); }); }; _ctor.prototype.onInitWhiteName = function () { return __awaiter(this, undefined, undefined, function () { var e; return __generator(this, function () { e = YPSDK.platformUrl + "/User/GetCfgData?userId=" + YPSDK.Platform.loginData.userUid + "&keyId=NoVideo"; console.log("初始化白名单", YPSDK.platformUrl, YPSDK.Platform.loginData.userUid, e); r_HttpManager.HttpMgr.requestData(function (e) { r_LogManager.LogMgr.debug("---后台请求白名单数据-->>>>>>res------", e); e && e.data && e.data.keyData && "true" == e.data.keyData && (r_PlayerDataManager.PlayerDataMgr.WHITE_NAME_NO_VIDEO = true); }, e); return [2]; }); }); }; _ctor.prototype._loadUserData = function () { var e = this; return new Promise(function (t) { return __awaiter(e, undefined, undefined, function () { var e; return __generator(this, function (o) { switch (o.label) { case 0: o.trys.push([0, 2,, 3]); console.error("初始化数据"); r_AudioManager.AudioMgr.init(); r_GameGlobalVariable.GameGlobalVariable.initPeiZhi(); YPSDK.Common.curChannelData.platformType != YPSDK.GamePlatformType.WX && YPSDK.Common.curChannelData.platformType != YPSDK.GamePlatformType.TT || r_YpNetMag.YpNetMag.failSever(function () { r_YpNetMag.YpNetMag.isFail = true; t(true); console.error("数据读取失败"); }); return [4, r_YpNetMag.YpNetMag.initServer(YPSDK.Platform.loginData)]; case 1: o.sent(); return [3, 3]; case 2: e = o.sent(); r_LogManager.LogMgr.error("load user data error:", e); return [3, 3]; case 3: t(true); return [2]; } }); }); }); }; _ctor.prototype._setProgress = function (e) { e = Math.max(0, Math.min(1, e)); this.progress_loading.progress = e; }; __decorate([_property(cc.ProgressBar)], _ctor.prototype, "progress_loading", undefined); return __decorate([_ccclass], _ctor); }(r_UIView.default); exports.default = def_UI_Entry;这个是我的entry代码 结合上述所有问题 给我修改成最终版本
最新发布
07-15
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

helloworddm

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值