From 25603b447119f7d1a6f8b43f3a4959cf8d188595 Mon Sep 17 00:00:00 2001 From: lh <2334563547@qq.com> Date: Thu, 27 Aug 2026 10:16:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(numerical):=20=E4=BF=AE=E5=A4=8D=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E5=88=86=E6=9E=90=E6=97=B6=E7=BD=91=E6=A0=BC=E7=AA=97?= =?UTF-8?q?=E5=8F=A3=E7=BB=91=E5=AE=9A=E6=97=A7=E6=95=B0=E6=8D=AE=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 统一切换网格窗口的 DataManager - 丢弃旧分析的排队信号和过期网格任务结果 - 切换时更新网格参数并清理旧 VTK 视图 --- Include/nmNum/nmSubWnd/nmSubWndGrid.h | 8 ++ Src/nmNum/nmSubWnd/nmSubWndGrid.cpp | 124 ++++++++++++++++-- .../nmSubWndSubWxsAnalyzeController.cpp | 5 +- Src/nmNum/nmSubWnd/nmSubWndUtils.cpp | 13 +- 4 files changed, 124 insertions(+), 26 deletions(-) diff --git a/Include/nmNum/nmSubWnd/nmSubWndGrid.h b/Include/nmNum/nmSubWnd/nmSubWndGrid.h index 1166203..83079b7 100644 --- a/Include/nmNum/nmSubWnd/nmSubWndGrid.h +++ b/Include/nmNum/nmSubWnd/nmSubWndGrid.h @@ -25,6 +25,9 @@ class NM_SUB_WND_EXPORT nmSubWndGrid : public iSubWndBaseFit { nmDataAnalyzeManager* pDataManager = nullptr); ~nmSubWndGrid(); + /** @brief 切换窗口所属数据管理器,并安全处理仍在运行的旧网格任务。 */ + void setDataManager(nmDataAnalyzeManager* pDataManager); + // 配置窗体 virtual void configWnd(QString sPrevInfo); @@ -38,6 +41,8 @@ class NM_SUB_WND_EXPORT nmSubWndGrid : public iSubWndBaseFit { virtual bool onConfirmClosing(); public slots: + /** @brief 只响应当前数据管理器的数据变化,忽略切换前已排队的旧信号。 */ + void onDataManagerChanged(); /** @brief 合并连续数据变化请求,并启动本窗口的防抖计时。 */ void updateGrid(); /** @brief 防抖结束后启动或排队一次后台 PEBI 网格任务。 */ @@ -61,6 +66,8 @@ class NM_SUB_WND_EXPORT nmSubWndGrid : public iSubWndBaseFit { void showGridResult(); /** @brief 网格失败时恢复旧网格;没有旧网格则显示明确失败提示。 */ void showGridFailure(); + /** @brief 销毁当前 VTK 页,防止切换分析后继续显示旧分析网格。 */ + void clearGridResultView(); QStackedLayout* m_mainLayout; ///< 在加载背景和 VTK 结果之间切换的主布局。 nmWxGridVTKContainerWidget* m_vtkWidget; ///< 显示所属分析网格的 VTK 控件。 @@ -68,6 +75,7 @@ class NM_SUB_WND_EXPORT nmSubWndGrid : public iSubWndBaseFit { QTimer* m_pDebounceTimer; ///< 本窗口独立防抖定时器,避免跨窗口刷新。 QPointer m_pDataManager; ///< 窗口所属数据管理器,销毁后自动置空。 QPointer m_pGridTask; ///< 当前后台网格任务,完成后自动回收。 + QPointer m_pGridTaskDataManager; ///< 当前网格任务创建时绑定的数据管理器。 bool m_bGridUpdatePending; ///< 任务运行期间是否又收到网格更新请求。 // PEBI GridControl控制参数,对应HX_NWTM_GRID_INPUT::GridControl diff --git a/Src/nmNum/nmSubWnd/nmSubWndGrid.cpp b/Src/nmNum/nmSubWnd/nmSubWndGrid.cpp index ea29e31..3c0ba41 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndGrid.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndGrid.cpp @@ -64,14 +64,16 @@ nmSubWndGrid::nmSubWndGrid(QWidget *parent, m_vtkWidget(nullptr), m_pGridLoadingLabel(nullptr), m_pDebounceTimer(nullptr), - m_pDataManager(pDataManager), + m_pDataManager(nullptr), m_pGridTask(nullptr), + m_pGridTaskDataManager(nullptr), m_bGridUpdatePending(false), m_dPebiGridControl(pebiGridControl), m_bGeneratePebiGrid(bGeneratePebiGrid) { m_sMdiWndType = s_MdiType_DataAnal; setWindowTitle(tr("nmSubWndGrid")); + setDataManager(pDataManager); } @@ -84,6 +86,65 @@ nmSubWndGrid::~nmSubWndGrid() } } +void nmSubWndGrid::setDataManager(nmDataAnalyzeManager* pDataManager) +{ + nmDataAnalyzeManager* pOldDataManager = m_pDataManager.data(); + if(pOldDataManager == pDataManager) { + return; + } + + // 第一步:只解除旧分析与本窗口的连接,避免切换多次后旧分析继续触发重建。 + if(pOldDataManager != nullptr) { + disconnect(pOldDataManager, SIGNAL(dataChanged()), + this, SLOT(onDataManagerChanged())); + } + + m_pDataManager = pDataManager; + if(pDataManager != nullptr) { + connect(pDataManager, SIGNAL(dataChanged()), + this, SLOT(onDataManagerChanged()), + static_cast( + Qt::QueuedConnection | Qt::UniqueConnection)); + } + + // 构造阶段保留对话框传入的控制参数;窗口创建完成后的分析切换使用新分析自己的值。 + if(m_mainLayout != nullptr && pDataManager != nullptr) { + m_dPebiGridControl = pDataManager->getPebiGridControl(); + } + + if(m_pDebounceTimer != nullptr) { + m_pDebounceTimer->stop(); + } + + // 第二步:旧 VTK 页不能作为新分析的失败回退页,否则会继续显示上一分析的网格。 + clearGridResultView(); + if(m_mainLayout == nullptr) { + return; + } + + if(pDataManager == nullptr) { + m_bGridUpdatePending = false; + showGridFailure(); + return; + } + + if(pDataManager->isPebiGridValid()) { + m_bGridUpdatePending = false; + showGridResult(); + return; + } + + showGridLoading(); + if(!m_pGridTask.isNull()) { + // 外部 DLL 不能安全中止;旧任务自然结束后只补做当前分析的最后一次更新。 + m_bGridUpdatePending = true; + return; + } + + m_bGridUpdatePending = false; + updateGrid(); +} + void nmSubWndGrid::configWnd(QString sPrevInfo) { iSubWndBaseFit::configWnd(sPrevInfo); @@ -204,6 +265,15 @@ bool nmSubWndGrid::generatePebiGrid() return true; } +void nmSubWndGrid::onDataManagerChanged() +{ + // 旧分析在断开前可能已经把信号排入主线程队列,发送者不匹配时不得刷新当前分析。 + if(sender() != m_pDataManager.data()) { + return; + } + updateGrid(); +} + void nmSubWndGrid::updateGrid() { // 角色、产量等只影响求解的变化也会发送 dataChanged;网格仍有效时无需重划。 @@ -226,8 +296,8 @@ void nmSubWndGrid::updateGrid() void nmSubWndGrid::onDebounceTimeout() { - // 第一步:同一窗口只允许一个网格线程运行,连续编辑合并为下一次重建。 - if(!m_pGridTask.isNull() && m_pGridTask->isRunning()) { + // 第一步:任务结束信号回到主线程前仍视为占用,避免覆盖尚未处理的任务指针。 + if(!m_pGridTask.isNull()) { m_bGridUpdatePending = true; return; } @@ -240,6 +310,7 @@ void nmSubWndGrid::onDebounceTimeout() // 第二步:把窗口参数写回所属分析方案,再启动绑定同一管理器的后台任务。 nmCalculationPebiGrid::getInstance()->setGridControl( m_dPebiGridControl, pDataManager); + m_pGridTaskDataManager = pDataManager; m_pGridTask = new nmCalculationPebiGridTask(pDataManager, this); connect(m_pGridTask, SIGNAL(pebiGridGenerated(bool)), this, SLOT(onPebiGridGenerated(bool)), Qt::QueuedConnection); @@ -256,9 +327,18 @@ void nmSubWndGrid::onPebiGridGenerated(bool success) // 局部结果;如果用户在划分期间修改了输入,commitResult() 会按版本拒绝。 nmCalculationPebiGridTask* pCompletedTask = qobject_cast(sender()); - nmDataAnalyzeManager* pDataManager = m_pDataManager.data(); - if(success && pCompletedTask != nullptr && pDataManager != nullptr) { - success = pCompletedTask->commitResult(pDataManager); + nmDataAnalyzeManager* pTaskDataManager = + m_pGridTaskDataManager.data(); + + // 只处理窗口当前持有且仍属于当前分析的任务;切换前启动的旧任务直接丢弃。 + if(pCompletedTask != m_pGridTask.data() || + pTaskDataManager == nullptr || + pTaskDataManager != m_pDataManager.data()) { + return; + } + + if(success) { + success = pCompletedTask->commitResult(pTaskDataManager); } else { success = false; } @@ -275,13 +355,22 @@ void nmSubWndGrid::onPebiGridGenerated(bool success) void nmSubWndGrid::onPebiGridTaskFinished() { // 第一步:只清理由当前窗口持有的任务,避免旧 finished 信号覆盖新任务指针。 - if(sender() == m_pGridTask.data()) { - m_pGridTask = nullptr; + if(sender() != m_pGridTask.data()) { + return; } + m_pGridTask = nullptr; + m_pGridTaskDataManager = nullptr; // 第二步:运行期间若又有数据变化,重新启动防抖计时,只补做最后一次网格。 if(m_bGridUpdatePending) { m_bGridUpdatePending = false; + nmDataAnalyzeManager* pDataManager = m_pDataManager.data(); + if(pDataManager != nullptr && + pDataManager->isPebiGridValid() && + m_vtkWidget == nullptr) { + showGridResult(); + return; + } updateGrid(); } } @@ -304,11 +393,7 @@ void nmSubWndGrid::showGridResult() // 网格已经在主线程完整提交,此处一次创建新的 VTK 页,不会向用户暴露 // 没有 RenderWindow 的中间状态。 - if(m_vtkWidget != nullptr) { - m_mainLayout->removeWidget(m_vtkWidget); - delete m_vtkWidget; - m_vtkWidget = nullptr; - } + clearGridResultView(); m_vtkWidget = new nmWxGridVTKContainerWidget( m_pDataManager.data(), centralWidget()); @@ -318,6 +403,19 @@ void nmSubWndGrid::showGridResult() m_mainLayout->setCurrentWidget(m_vtkWidget); } +void nmSubWndGrid::clearGridResultView() +{ + if(m_vtkWidget == nullptr) { + return; + } + + if(m_mainLayout != nullptr) { + m_mainLayout->removeWidget(m_vtkWidget); + } + delete m_vtkWidget; + m_vtkWidget = nullptr; +} + void nmSubWndGrid::showGridFailure() { if(m_mainLayout == nullptr) { diff --git a/Src/nmNum/nmSubWnd/nmSubWndSubWxsAnalyzeController.cpp b/Src/nmNum/nmSubWnd/nmSubWndSubWxsAnalyzeController.cpp index 1292a45..ddc0c37 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndSubWxsAnalyzeController.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndSubWxsAnalyzeController.cpp @@ -61,9 +61,8 @@ void nmSubWndSubWxsAnalyzeController::updateWindowsForCurrentAnalyze(ZxMainWindo nmSubWndGrid* pSubWndGrid = dynamic_cast(pSub); if(nullptr != pSubWndGrid) { - QObject::disconnect(pManager, SIGNAL(dataChanged()), pSubWndGrid, SLOT(updateGrid())); - QObject::connect(pManager, SIGNAL(dataChanged()), pSubWndGrid, SLOT(updateGrid()), Qt::QueuedConnection); - pSubWndGrid->updateGrid(); + // 网格窗口负责切换所属数据管理器,并丢弃切换前任务的过期结果。 + pSubWndGrid->setDataManager(pManager); } } } diff --git a/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp b/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp index 91940ca..f585005 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp @@ -228,10 +228,6 @@ iSubWnd* nmSubWndUtils::makesureSubWnd(QString sID, QString sExt, \ nmSubWndGrid* pSubWndGrid = new nmSubWndGrid( NULL, sExt, dPebiGridControl, bGeneratePebiGrid, pDataManager); - disconnect(pDataManager, SIGNAL(dataChanged()), - pSubWndGrid, SLOT(updateGrid())); - connect(pDataManager, SIGNAL(dataChanged()), - pSubWndGrid, SLOT(updateGrid()), Qt::QueuedConnection); pSubWnd = pSubWndGrid; } else if(nID == 5116) { // 后处理 @@ -1146,15 +1142,12 @@ void nmSubWndUtils::slotHandleTabChange(int index) pMap->updateMapByDataManager(pDataManger); } - // TODO:网格更新 + // 网格窗口跟随当前分析切换数据来源。 nmSubWndGrid* pSubWndGrid = dynamic_cast(pSub); if(nullptr != pSubWndGrid) { - // 更新网格 - disconnect(pDataManger, SIGNAL(dataChanged()), pSubWndGrid, SLOT(updateGrid())); - connect(pDataManger, SIGNAL(dataChanged()), pSubWndGrid, SLOT(updateGrid()), Qt::QueuedConnection); - // 手动更新 - pSubWndGrid->updateGrid(); + // 由网格窗口统一解除旧连接、绑定新分析并处理旧后台任务。 + pSubWndGrid->setDataManager(pDataManger); } } }