diff --git a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h index 25de48bb..07986e45 100644 --- a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h +++ b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h @@ -13,7 +13,6 @@ #include #include "nmCalculationDefine.h" #include "nmCalculation_global.h" -#include "pch.h" class nmDataAnalyzeManager; class nmDataSensitive; @@ -22,6 +21,9 @@ class vtkDoubleArray; struct nmPebiSolverInputSnapshot; struct nmPebiManualCaptureState; struct nmPebiStaticPropertyValues; +// DLL 类型只用于私有函数的引用参数,界面模块无需包含求解器内部头文件。 +struct HX_NWTM_MODEL_INPUT; +struct HX_NWTM_MODEL_OUTPUT; // 主窗口现在直接创建DLL求解线程任务,因此类需要导出,供nmSubWnd模块跨DLL使用。 class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { @@ -42,7 +44,7 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { bool bDeferManualSnapshot = false); ~nmCalculationDllPebiSolverTask(); - /** @brief QThread 入口,记录本次 execute() 的成功状态并发送完成信号。 */ + /** @brief QThread 入口,执行单次或敏感性批量求解并发送一次完成信号。 */ void run(); // PSO 等待线程结束后通过该接口判断本次结果是否可以继续参与误差计算. // 返回 false 时调用方会丢弃本次结果, 防止复用上一粒子留下的旧曲线. @@ -106,13 +108,15 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { /** @brief 一组采样的临时返回值,参数顺序与采样配置的变量列表一致。 */ struct SensitivityRunResult { - SensitivityRunResult() : m_nRunIndex(0), m_bSucceeded(false) {} + SensitivityRunResult() + : m_nRunIndex(0), m_bSucceeded(false), m_bCancelled(false) {} quint64 m_nRunIndex; ///< 与预览一致的零基运行编号。 QVector m_vecParameters; ///< 本组参数的业务基准单位值。 QVector > m_vecPressure; QVector > m_vecLogLog; QVector > m_vecSemiLog; bool m_bSucceeded; + bool m_bCancelled; ///< 求解中收到停止请求,本组曲线已丢弃。 QString m_sFailureReason; }; @@ -128,10 +132,22 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { std::vector& results, QString* errorMessage = nullptr) const; + /** @brief 在源输入捕获线程创建独立批量任务,随后由 start() 在后台执行。 */ + nmCalculationDllPebiSolverTask* createSensitivityBatch( + const nmDataSensitive& sampling, QString* errorMessage = nullptr) const; + /** @brief 线程结束后读取临时批量结果,引用在任务析构或重新运行后失效。 */ + const std::vector& getSensitivityResults() const; + /** @brief 线程结束后判断全部组合是否处理完成,单组失败不影响此标记。 */ + bool wasSensitivityBatchCompleted() const; + private: /** @brief 仅复制目标井求解的值快照,不登记或访问实时管理器。 */ nmCalculationDllPebiSolverTask(const nmCalculationDllPebiSolverTask& source, QObject* parent); + /** @brief 在当前线程执行一组输入,更新状态但不发送整次任务完成信号。 */ + void calculateSingleRun(); + /** @brief 复用当前独立副本串行计算,保留进度、停止状态及每组失败原因。 */ + bool executeSensitivityBatch(); /** @brief 执行当前唯一的 PEBI 求解模式。 */ bool execute(); /** @brief 成对释放构造时登记的 DataManager 后台使用权。 */ @@ -206,6 +222,10 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { bool m_bPendingFullResultReady; ///< 是否已有可由主线程一次提交的完整结果。 QAtomicInt m_nCancelRequested; ///< 跨线程协作式停止标记,1 表示请求停止。 QAtomicInt m_nWasCancelled; ///< run() 已按停止请求丢弃本次局部结果。 + QSharedPointer m_pSensitivitySampling; ///< 批量启动前冻结的配置。 + std::vector m_vecSensitivityResults; ///< 已处理组的临时结果。 + QString m_sSensitivityFailureReason; ///< 整次批量无法继续执行的原因。 + bool m_bSensitivityBatchCompleted; ///< 全部组合是否已处理,停止时为 false。 private slots: //void slotTaskUpdateProgress(); @@ -216,6 +236,8 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { void sig_calculateDone(bool isSuccessed); /** @brief 报告当前真实阶段;nTotal<=0 表示 DLL 等不可测阶段。 */ void sigStageChanged(QString sStage, int nCurrent, int nTotal); + /** @brief 敏感性已处理组数、总组数和失败组数,按时间节流发送。 */ + void sigSensitivityProgress(quint64 nProcessed, quint64 nTotal, quint64 nFailed); //void sigTaskProgressUpdated(int progress); //void sigResSolverProgressUpdated(int progress); }; diff --git a/Include/nmNum/nmSubWnd/nmSubWndMain.h b/Include/nmNum/nmSubWnd/nmSubWndMain.h index 99ecc766..7d665d48 100644 --- a/Include/nmNum/nmSubWnd/nmSubWndMain.h +++ b/Include/nmNum/nmSubWnd/nmSubWndMain.h @@ -19,6 +19,7 @@ class QToolBar; class nmDataWellBase; class nmDataAnalyzeManager; class nmCalculationDllPebiSolverTask; +class nmWxSensitive; class iSubWndFitting; class QWidget; class QObject; @@ -161,6 +162,10 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit { void onSolverStageChanged(QString sStage, int nCurrent, int nTotal); /** @brief 请求当前手工任务协作式停止。 */ void onSolverCancelRequested(); + /** @brief 接管敏感性批量任务,使用当前窗口的普通求解进度框。 */ + void onSensitivityTaskReady(nmWxSensitive* dialog, nmCalculationDllPebiSolverTask* task); + void onSensitivityTaskFinished(); + void onSensitivityTaskDestroyed(); /** @brief 当前分析的建网提交结束后,恢复待启动或待捕获的求解流程。 */ void onPebiGridGenerationFinished(quint64 nGridInputRevision, bool bSucceeded); @@ -278,6 +283,8 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit { bool waitForPebiGridGeneration(); /** @brief 取消当前求解上下文对建网完成信号的订阅。 */ void disconnectPebiGridGenerationWait(); + /** @brief 普通求解和敏感性分析共用同一个进度框实例及初始化路径。 */ + void showSolverProgress(); // 绘图相关 nmGuiPlot* m_pWxPlot; @@ -301,6 +308,8 @@ private: QProgressDialog* m_pProgressDlg; nmCalculationDllPebiSolverTask* m_pSolverTask; ///< 当前窗口持有的 DLL 求解线程任务。 + // 批量任务仍由敏感性窗口持有,这里只绑定进度和停止操作。 + QPointer m_pSensitivityTask; /** @brief 启动求解时捕获的所属数据管理器,防止完成时活动窗口已经切换。 */ QPointer m_pSolverDataManager; /** @brief 启动求解时捕获的拟合窗口,只把结果合并回该窗口。 */ diff --git a/Include/nmNum/nmSubWxs/nmWxNumericalDesign.h b/Include/nmNum/nmSubWxs/nmWxNumericalDesign.h index 58f72423..d8ccd8fa 100644 --- a/Include/nmNum/nmSubWxs/nmWxNumericalDesign.h +++ b/Include/nmNum/nmSubWxs/nmWxNumericalDesign.h @@ -21,6 +21,8 @@ class nmDataWellBase; class nmDataDiagnostic; class nmDataReservoir; class nmDataAnalyzeManager; +class nmWxSensitive; +class nmCalculationDllPebiSolverTask; /** * @brief 生成数值模型Dock面板。 @@ -69,6 +71,8 @@ public: signals: void sigGenerateClicked(); // 点击生成按钮时发出的信号 + // 敏感性批量任务交给所属主窗口,复用普通求解进度框。 + void sigSensitivityTaskReady(nmWxSensitive* dialog, nmCalculationDllPebiSolverTask* task); void sigIncludeWells(); // 包含其他井后刷新参数栏的信号 /** @brief 用户切换结果井时发送快照中的稳定井 UUID。 */ void sigResultWellChanged(const QString& sWellInstanceId); diff --git a/Include/nmNum/nmSubWxs/nmWxSensitive.h b/Include/nmNum/nmSubWxs/nmWxSensitive.h index dc899b3f..edf25a99 100644 --- a/Include/nmNum/nmSubWxs/nmWxSensitive.h +++ b/Include/nmNum/nmSubWxs/nmWxSensitive.h @@ -1,4 +1,4 @@ -#ifndef NMWXSENSITIVE_H +#ifndef NMWXSENSITIVE_H #define NMWXSENSITIVE_H #include "iDlgBase.h" @@ -19,6 +19,8 @@ class QTableWidget; class QLabel; class QIcon; class SensitivityRunsView; +class nmCalculationDllPebiSolverTask; +class QCloseEvent; class NM_SUB_WXS_EXPORT nmWxSensitive : public iDlgBase { @@ -27,6 +29,18 @@ class NM_SUB_WXS_EXPORT nmWxSensitive : public iDlgBase public: explicit nmWxSensitive(QWidget *parent = 0, nmDataAnalyzeManager* manager = nullptr); ~nmWxSensitive(); + // 主窗口接管普通求解进度框后,才隐藏参数窗口并启动批量任务。 + void startSensitivityCalculation(); + +signals: + void sigSensitivityTaskReady(nmWxSensitive* dialog, nmCalculationDllPebiSolverTask* task); + void sigSensitivityCancelRequested(); + +public slots: + void reject() override; + +protected: + void closeEvent(QCloseEvent* event) override; private slots: void onGenerateClicked(); @@ -39,6 +53,7 @@ public: void onTreeItemChanged(QTreeWidgetItem *item, int column); void onPreviewClicked(); void onPreviewScrollAction(int action); + void onSensitivityFinished(); private: void setupUI(); @@ -53,6 +68,7 @@ private: bool saveDataFromUI(); // 校验并从界面保存到数据 void refreshModelValues(); void updateModelCount(); + void setCalculationRunning(bool running); int findVariableIndex(const QString& varName); // 查找变量索引 // UI 控件 @@ -79,6 +95,8 @@ private: bool m_loading; nmDataAnalyzeManager* m_pDataManager; SensitivityRunsView* m_previewView; + nmCalculationDllPebiSolverTask* m_pSensitivityTask; + bool m_calculationRunning; // 敏感分析数据 nmDataSensitive m_sensitiveData; diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp index 6af0b7d1..bf4749ff 100644 --- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp @@ -1,4 +1,5 @@ #include "nmCalculationDllPebiSolverTask.h" +#include "pch.h" #include "zxLogInstance.h" #include "nmDataAnalyzeManager.h" #include "nmDataSensitive.h" @@ -28,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -173,7 +175,7 @@ public: if(pCancelRequested == NULL) { pMutex->lock(); } else { - // 只让手工任务在等待全局 DLL 锁时可停止,互斥范围和锁顺序不变。 + // 手工与敏感性批量任务等待全局 DLL 锁时可停止,互斥范围不变。 while(!pMutex->tryLock(100)) { if(isTaskCancellationRequested(pCancelRequested)) { return false; @@ -651,7 +653,8 @@ nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask( m_dPendingScalarMax(0.0), m_bPendingFullResultReady(false), m_nCancelRequested(0), - m_nWasCancelled(0) + m_nWasCancelled(0), + m_bSensitivityBatchCompleted(false) { try { m_pInputSnapshot = new nmPebiSolverInputSnapshot(); @@ -707,7 +710,8 @@ nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask( m_lastRunSucceeded(false), m_nSolveTimeMs(-1), m_nPebiCount(-1), m_sAutoFitTargetWellCode(source.m_sAutoFitTargetWellCode), m_dPendingScalarMin(0.0), m_dPendingScalarMax(0.0), - m_bPendingFullResultReady(false), m_nCancelRequested(0), m_nWasCancelled(0) + m_bPendingFullResultReady(false), m_nCancelRequested(0), m_nWasCancelled(0), + m_bSensitivityBatchCompleted(false) { // 采样任务只持有自己的值快照;源任务和分析窗口可独立释放。 } @@ -766,8 +770,9 @@ bool nmCalculationDllPebiSolverTask::updateSensitivityRun( const nmDataSensitive& sampling, quint64 runIndex, QString* errorMessage) { if(errorMessage) errorMessage->clear(); - // 更新必须发生在两次求解之间,不能用于原始任务或实时管理器输入。 - if(isRunning() || m_pDataManager != nullptr || !m_pInputSnapshot || + // 更新只在两组之间进行;运行中的批量任务仅允许自己的工作线程更新。 + if((isRunning() && (m_pSensitivitySampling.isNull() || QThread::currentThread() != this)) || + m_pDataManager != nullptr || !m_pInputSnapshot || !m_pInputSnapshot->m_bAutoFitTargetOnly || m_pInputSnapshot->m_oSensitivityBaseline.m_nTarget < 0) { if(errorMessage) *errorMessage = tr("Sensitivity task is unavailable or still running."); @@ -781,8 +786,11 @@ bool nmCalculationDllPebiSolverTask::updateSensitivityRun( m_sFailureReason.clear(); m_nSolveTimeMs = -1; m_nPebiCount = -1; - m_nCancelRequested.fetchAndStoreOrdered(0); - m_nWasCancelled.fetchAndStoreOrdered(0); + // 批量任务的停止标记持续到整次结束,不能在更新下一组时丢失界面的请求。 + if(m_pSensitivitySampling.isNull()) { + m_nCancelRequested.fetchAndStoreOrdered(0); + m_nWasCancelled.fetchAndStoreOrdered(0); + } QVector values; if(!sampling.usesBaseUnits() || !sampling.getRunValues(runIndex, values)) { fail(tr("Invalid sensitivity sampling configuration.")); @@ -862,9 +870,22 @@ bool nmCalculationDllPebiSolverTask::calculateSensitivityBatch( QString* errorMessage) const { results.clear(); + // 同步入口与后台入口共用批量循环,只转移结果容器,不复制整批曲线。 + QScopedPointer task( + createSensitivityBatch(sampling, errorMessage)); + if(!task) return false; + task->run(); + results.swap(task->m_vecSensitivityResults); + if(errorMessage) *errorMessage = task->m_sSensitivityFailureReason; + return task->m_bSensitivityBatchCompleted; +} + +nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivityBatch( + const nmDataSensitive& sampling, QString* errorMessage) const +{ if(errorMessage) errorMessage->clear(); try { - // 冻结整次批量的配置,次数、运行顺序和返回参数都使用同一份采样数据。 + // 所有配置与模型复制都在启动线程前完成,后台只读取独立的值输入。 const nmDataSensitive frozenSampling(sampling); bool overflow = false; const quint64 count = frozenSampling.calculateModelCount(&overflow); @@ -873,7 +894,7 @@ bool nmCalculationDllPebiSolverTask::calculateSensitivityBatch( (type != nmDataSensitive::CALC_DETERMINISTIC && type != nmDataSensitive::CALC_DETERMINISTIC_MULTIVARIATE)) { if(errorMessage) *errorMessage = tr("Invalid sensitivity sampling configuration."); - return false; + return nullptr; } // 只在整次批量开始时复制模型与网格;首组参数由创建接口填入。 @@ -882,47 +903,117 @@ bool nmCalculationDllPebiSolverTask::calculateSensitivityBatch( createSensitivityRun(frozenSampling, 0, &creationError)); if(!task) { if(errorMessage) *errorMessage = creationError; - return false; + return nullptr; } + task->m_pSensitivitySampling = QSharedPointer( + new nmDataSensitive(frozenSampling)); + return task.take(); + } catch(const std::bad_alloc&) { + if(errorMessage) *errorMessage = tr("Insufficient memory to prepare solver input."); + return nullptr; + } +} + +const std::vector& +nmCalculationDllPebiSolverTask::getSensitivityResults() const +{ + // 调用方须先等待线程结束,运行中的结果容器不向界面开放读取。 + return m_vecSensitivityResults; +} + +bool nmCalculationDllPebiSolverTask::wasSensitivityBatchCompleted() const +{ + return m_bSensitivityBatchCompleted; +} + +bool nmCalculationDllPebiSolverTask::executeSensitivityBatch() +{ + m_vecSensitivityResults.clear(); + m_sSensitivityFailureReason.clear(); + const quint64 count = m_pSensitivitySampling->calculateModelCount(); + quint64 failed = 0; + QElapsedTimer progressTimer; + progressTimer.start(); + emit sigSensitivityProgress(0, count, 0); + try { for(quint64 runIndex = 0; runIndex < count; ++runIndex) { + if(isCancelRequested()) return false; SensitivityRunResult result; result.m_nRunIndex = runIndex; // 单参数法保持其余参数的模型值,多参数法按预览组合顺序逐组取值。 - bool inputReady = frozenSampling.getRunValues(runIndex, result.m_vecParameters); + bool inputReady = m_pSensitivitySampling->getRunValues(runIndex, result.m_vecParameters); if(!inputReady) { result.m_sFailureReason = tr("Invalid sensitivity sampling configuration."); - } else if(runIndex > 0) { + } else { // 本组更新失败仍保留运行编号,下一组继续从冻结基准更新。 - inputReady = task->updateSensitivityRun(frozenSampling, runIndex, - &result.m_sFailureReason); + inputReady = updateSensitivityRun(*m_pSensitivitySampling, runIndex, + &result.m_sFailureReason); } if(inputReady) { - task->run(); - result.m_bSucceeded = task->wasSuccessful(); + calculateSingleRun(); + result.m_bSucceeded = m_lastRunSucceeded; if(result.m_bSucceeded) { - result.m_vecPressure = task->getAutoFitResultPressure(); - result.m_vecLogLog = task->getAutoFitResultLogLog(); - result.m_vecSemiLog = task->getAutoFitResultSemiLog(); + result.m_vecPressure = m_autoFitResultPressure; + result.m_vecLogLog = m_autoFitResultLogLog; + result.m_vecSemiLog = m_autoFitResultSemiLog; } else { - result.m_sFailureReason = task->getFailureReason(); + result.m_sFailureReason = m_sFailureReason; } } + if(isCancelRequested()) { + result.m_bCancelled = true; + result.m_bSucceeded = false; + result.m_sFailureReason.clear(); + result.m_vecPressure.clear(); + result.m_vecLogLog.clear(); + result.m_vecSemiLog.clear(); + } else if(!result.m_bSucceeded) { + ++failed; + } // Qt 曲线容器按值保留;下一组清空任务结果不会清掉这里的曲线。 // 不预先展开全部组合或预分配总次数,也不合并重复的基准组合。 - results.push_back(result); + m_vecSensitivityResults.push_back(result); + // 快速失败的多组输入也限频通知,避免排队的进度信号堵塞界面。 + if(progressTimer.elapsed() >= 100 || runIndex + 1 == count || isCancelRequested()) { + emit sigSensitivityProgress(runIndex + 1, count, failed); + progressTimer.restart(); + } + if(isCancelRequested()) return false; } return true; } catch(const std::bad_alloc&) { - if(errorMessage) *errorMessage = tr("Insufficient memory to process solver results."); + m_sSensitivityFailureReason = tr("Insufficient memory to process solver results."); return false; } catch(const std::length_error&) { - if(errorMessage) *errorMessage = tr("Insufficient memory to process solver results."); + m_sSensitivityFailureReason = tr("Insufficient memory to process solver results."); return false; } } void nmCalculationDllPebiSolverTask::run() +{ + if(!m_pSensitivitySampling.isNull()) { + // 同一后台线程逐组执行,仅在整次结束后发送完成信号。 + m_bSensitivityBatchCompleted = executeSensitivityBatch(); + m_lastRunSucceeded = m_bSensitivityBatchCompleted; + for(size_t i = 0; m_lastRunSucceeded && i < m_vecSensitivityResults.size(); ++i) + m_lastRunSucceeded = m_vecSensitivityResults[i].m_bSucceeded; + if(isCancelRequested()) { + m_bSensitivityBatchCompleted = false; + m_lastRunSucceeded = false; + m_nWasCancelled.fetchAndStoreOrdered(1); + } + discardPendingFullResult(); + } else { + calculateSingleRun(); + } + // 完成信号可能触发成果关闭;必须在发信号前结束 DataManager 使用期。 + releaseDataManagerUse(); + emit sig_calculateDone(m_lastRunSucceeded); +} + +void nmCalculationDllPebiSolverTask::calculateSingleRun() { m_nSolveTimeMs = -1; m_nPebiCount = -1; @@ -948,9 +1039,6 @@ void nmCalculationDllPebiSolverTask::run() bSucceeded = false; } m_lastRunSucceeded = bSucceeded; - // 完成信号可能触发成果关闭;必须在发信号前结束 DataManager 使用期。 - releaseDataManagerUse(); - emit sig_calculateDone(m_lastRunSucceeded); } void nmCalculationDllPebiSolverTask::requestCancel() @@ -1209,7 +1297,7 @@ void nmCalculationDllPebiSolverTask::reportStage( int nTotal) { if(m_pInputSnapshot != nullptr && - !m_pInputSnapshot->m_bAutoFitTargetOnly) { + (!m_pInputSnapshot->m_bAutoFitTargetOnly || !m_pSensitivitySampling.isNull())) { emit sigStageChanged(sStage, nCurrent, nTotal); } } @@ -1460,7 +1548,7 @@ int nmCalculationDllPebiSolverTask::getPebiCount() const QString nmCalculationDllPebiSolverTask::getFailureReason() const { - return m_sFailureReason; + return m_pSensitivitySampling.isNull() ? m_sFailureReason : m_sSensitivityFailureReason; } QVector > nmCalculationDllPebiSolverTask::getAutoFitResultPressure() const @@ -1497,7 +1585,7 @@ bool nmCalculationDllPebiSolverTask::execPebiMode() } const QAtomicInt* pCancelRequested = - m_pInputSnapshot->m_bAutoFitTargetOnly + m_pInputSnapshot->m_bAutoFitTargetOnly && m_pSensitivitySampling.isNull() ? NULL : &m_nCancelRequested; // 第一步:清空本次任务自己的输出。旧成果仍保留在 DataManager 中,只有主线程 @@ -1619,6 +1707,8 @@ bool nmCalculationDllPebiSolverTask::execPebiMode() pCancelRequested)) { return false; } + // 等待锁结束时再次检查停止请求,避免已经停止的批量组仍进入 DLL。 + if(isCancelRequested()) return false; HMODULE hModelModule = LoadLibrary(L"HX_NWTM.dll"); if(hModelModule == nullptr) { qWarning() << "Failed to load HX_NWTM.dll. Error code:" diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp index d4cbb02d..4bf89e2d 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp @@ -77,6 +77,7 @@ #include "nmWxParaPropertyPebi.h" #include "nmWxNumericalDesign.h" +#include "nmWxSensitive.h" #include "nmWxReservoirProperties.h" @@ -218,6 +219,14 @@ nmSubWndMain::~nmSubWndMain() s_pPendingSolverWindow = nullptr; } + // 批量任务由敏感性窗口释放;主窗口销毁前先解除回调并等待停止。 + if(!m_pSensitivityTask.isNull()) { + disconnect(m_pSensitivityTask.data(), nullptr, this, nullptr); + m_pSensitivityTask->requestCancel(); + m_pSensitivityTask->wait(); + if(s_pRunningSolverTask == m_pSensitivityTask) s_pRunningSolverTask = nullptr; + m_pSensitivityTask = nullptr; + } if (m_pProgressDlg) { delete m_pProgressDlg; m_pProgressDlg = NULL; @@ -1640,16 +1649,6 @@ void nmSubWndMain::solveAndAnalyze() return; } - // Qt 4.8 下复用已经隐藏的进度框,避免窗口析构触发主界面重绘重入。 - if (m_pProgressDlg != nullptr) { - m_pProgressDlg->reset(); - QPushButton* pCancelButton = - m_pProgressDlg->findChild(); - if(pCancelButton != nullptr) { - pCancelButton->setEnabled(true); - } - } - // 新一次启动前清空旧上下文;后续所有检查通过后再成对写入。 m_pSolverDataManager = nullptr; m_pSolverFitting = nullptr; @@ -1765,6 +1764,15 @@ void nmSubWndMain::solveAndAnalyze() m_bSolverCancelRequested = false; // 第二步:先显示真实进度框,再在下一轮事件中捕获输入并启动线程。 + showSolverProgress(); + m_bSolverStartPending = true; + s_pPendingSolverWindow = this; + QTimer::singleShot(0, this, SLOT(startSolverTask())); +} + +void nmSubWndMain::showSolverProgress() +{ + // Qt 4.8 下保留已经隐藏的实例,普通求解与批量任务都从这里初始化。 if (m_pProgressDlg == nullptr) { m_pProgressDlg = new QProgressDialog( @@ -1788,6 +1796,9 @@ void nmSubWndMain::solveAndAnalyze() this, SLOT(onSolverCancelRequested())); } + m_pProgressDlg->reset(); + QPushButton* pCancelButton = m_pProgressDlg->findChild(); + if(pCancelButton != nullptr) pCancelButton->setEnabled(true); m_pProgressDlg->setLabelText(tr("Preparing input snapshot...")); m_pProgressDlg->setRange(0, 0); m_pProgressDlg->show(); @@ -1795,9 +1806,49 @@ void nmSubWndMain::solveAndAnalyze() m_pProgressDlg->raise(); // 提升层级到最前 m_pProgressDlg->activateWindow(); // 激活窗口焦点 - m_bSolverStartPending = true; - s_pPendingSolverWindow = this; - QTimer::singleShot(0, this, SLOT(startSolverTask())); +} + +void nmSubWndMain::onSensitivityTaskReady( + nmWxSensitive* dialog, nmCalculationDllPebiSolverTask* task) +{ + // 与普通求解共用防重入状态,避免两个任务同时写同一进度框。 + if(!s_pPendingSolverWindow.isNull() || !s_pRunningSolverTask.isNull() || + m_bSolverResultCompletionPending) { + QMessageBox::information(dialog, tr("solver error"), tr("task is running!")); + return; + } + m_pSensitivityTask = task; + s_pRunningSolverTask = task; + m_bSolverCancelRequested = false; + // 整个批次保持同一个滚动进度,不订阅每组求解的阶段刷新信号。 + // 先隐藏共享进度框,再让参数窗口的完成槽读取结果。 + connect(task, SIGNAL(finished()), this, SLOT(onSensitivityTaskFinished()), Qt::QueuedConnection); + connect(task, SIGNAL(destroyed(QObject*)), this, SLOT(onSensitivityTaskDestroyed())); + connect(dialog, SIGNAL(sigSensitivityCancelRequested()), + this, SLOT(onSolverCancelRequested()), Qt::UniqueConnection); + showSolverProgress(); + m_pProgressDlg->setLabelText(nmCalculationDllPebiSolverTask::tr("Solving model...")); + dialog->startSensitivityCalculation(); +} + +void nmSubWndMain::onSensitivityTaskFinished() +{ + // 完整退出线程后再释放进度框的占用,不进入普通求解的成果提交路径。 + if(m_pSensitivityTask.isNull() || sender() != m_pSensitivityTask.data()) return; + m_pSensitivityTask->wait(); + if(s_pRunningSolverTask == m_pSensitivityTask) s_pRunningSolverTask = nullptr; + disconnect(m_pSensitivityTask.data(), nullptr, this, nullptr); + m_pSensitivityTask = nullptr; + m_bSolverCancelRequested = false; + m_pProgressDlg->hide(); +} + +void nmSubWndMain::onSensitivityTaskDestroyed() +{ + // 参数面板提前销毁时,其析构函数已等待任务结束,解除共享进度框占用。 + m_pSensitivityTask = nullptr; + m_bSolverCancelRequested = false; + m_pProgressDlg->hide(); } void nmSubWndMain::startSolverTask() @@ -1914,6 +1965,7 @@ void nmSubWndMain::onSolverStageChanged( return; } + // 普通求解根据当前阶段更新;没有精确进度时滚动显示。 m_pProgressDlg->setLabelText(sStage); if(nTotal <= 0) { m_pProgressDlg->setRange(0, 0); @@ -1950,9 +2002,12 @@ void nmSubWndMain::onSolverCancelRequested() return; } - if(m_pSolverTask != nullptr) { + // 共享停止按钮只控制当前占用进度框的任务,DLL 返回后的处理各自保持原路径。 + nmCalculationDllPebiSolverTask* pRunningTask = !m_pSensitivityTask.isNull() + ? m_pSensitivityTask.data() : m_pSolverTask; + if(pRunningTask != nullptr && pRunningTask->isRunning()) { m_bSolverCancelRequested = true; - m_pSolverTask->requestCancel(); + pRunningTask->requestCancel(); if(m_pProgressDlg != nullptr) { // DLL 没有取消导出;若已进入 DLL,只能等待其返回后丢弃结果。 m_pProgressDlg->setLabelText( @@ -2352,7 +2407,7 @@ bool nmSubWndMain::onConfirmClosing() { // 正常关闭入口在求解结束前保持当前成果和 DataManager 存活;应用退出等绕过 // 该确认流程的路径仍由析构函数 wait() 兜底。 - if(m_bSolverStartPending || + if(m_bSolverStartPending || !m_pSensitivityTask.isNull() || (m_pSolverTask != nullptr && m_pSolverTask->isRunning())) { QMessageBox::information(this, tr("solver error"), tr("task is running!")); return false; diff --git a/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp b/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp index da71e834..c0ffab04 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp @@ -480,6 +480,8 @@ void nmSubWndUtils::setupNumericalDockWidgets(nmDataAnalyzeManager* pDataManager // 数值设计面板只发出操作请求,具体处理仍由主窗口完成。 connect(pAnalWx, SIGNAL(sigGenerateClicked()), pSubWndMain, SLOT(onGenerateButtonClicked())); + connect(pAnalWx, SIGNAL(sigSensitivityTaskReady(nmWxSensitive*,nmCalculationDllPebiSolverTask*)), + pSubWndMain, SLOT(onSensitivityTaskReady(nmWxSensitive*,nmCalculationDllPebiSolverTask*))); connect(pAnalWx, SIGNAL(sigResultWellChanged(QString)), pSubWndMain, SLOT(onWellSelected(QString))); diff --git a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp index 3b17d8bf..4f8d23e7 100644 --- a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp +++ b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp @@ -1445,6 +1445,9 @@ void nmWxNumericalDesign::onForecastClicked() void nmWxNumericalDesign::onSensitiveClicked() { nmWxSensitive* dlg = new nmWxSensitive(this, m_pDataManager); + // 按数值生成按钮的现有路径,将任务请求转发给所属主窗口。 + connect(dlg, SIGNAL(sigSensitivityTaskReady(nmWxSensitive*,nmCalculationDllPebiSolverTask*)), + this, SIGNAL(sigSensitivityTaskReady(nmWxSensitive*,nmCalculationDllPebiSolverTask*))); dlg->show(); } diff --git a/Src/nmNum/nmSubWxs/nmWxSensitive.cpp b/Src/nmNum/nmSubWxs/nmWxSensitive.cpp index 3ec2c9cd..5af4d769 100644 --- a/Src/nmNum/nmSubWxs/nmWxSensitive.cpp +++ b/Src/nmNum/nmSubWxs/nmWxSensitive.cpp @@ -1,10 +1,13 @@ -#include "nmWxSensitive.h" +#include "nmWxSensitive.h" #include "nmDataAnalyzeManager.h" #include "nmDataSensitive.h" #include "nmDataReservoir.h" #include "nmDataWellBase.h" #include "nmDataPerforation.h" +#include "nmCalculationDllPebiSolverTask.h" +#include "iSubWndFitting.h" +#include "mGui/mGuiAnal/iAnalRun.h" #include "iUnitHelper.h" #include "iUnitGroup.h" #include "iSysParaHelper.h" @@ -51,6 +54,11 @@ #include #include +#include +#include +#include + +#pragma comment(lib, "mGuiAnal.lib") #include #include @@ -464,7 +472,7 @@ private: nmWxSensitive::nmWxSensitive(QWidget *parent, nmDataAnalyzeManager* manager) : iDlgBase(parent), m_selectedValueIndex(0), m_loading(true), m_pDataManager(manager ? manager : nmDataAnalyzeManager::getCurrentInstance()), - m_previewView(nullptr) + m_previewView(nullptr), m_pSensitivityTask(nullptr), m_calculationRunning(false) { // 显式绑定所属分析窗口,后续切换窗口不会改变配置保存目标。 if (m_pDataManager) m_sensitiveData = m_pDataManager->getSensitiveDataCopy(); @@ -481,6 +489,30 @@ nmWxSensitive::nmWxSensitive(QWidget *parent, nmDataAnalyzeManager* manager) nmWxSensitive::~nmWxSensitive() { + // 父窗口强制销毁时先请求停止并等 DLL 返回,线程结束前不释放计算副本。 + if(m_pSensitivityTask) { + disconnect(m_pSensitivityTask, nullptr, this, nullptr); + m_pSensitivityTask->requestCancel(); + m_pSensitivityTask->wait(); + delete m_pSensitivityTask; + } +} + +void nmWxSensitive::reject() +{ + // Esc 与取消按钮使用相同的停止路径,保留已完成组供结束后查看。 + if(m_calculationRunning) onCancelClicked(); + else iDlgBase::reject(); +} + +void nmWxSensitive::closeEvent(QCloseEvent* event) +{ + if(m_calculationRunning) { + onCancelClicked(); + event->ignore(); + return; + } + iDlgBase::closeEvent(event); } void nmWxSensitive::refreshModelValues() @@ -683,7 +715,7 @@ void nmWxSensitive::updateModelCount() m_sensitiveData.setTotalModelCount(count); modelCountLabel->setText(overflow ? tr("Too many models") : QString::number(count)); previewButton->setEnabled(!overflow && count > 0); - generateButton->setEnabled(m_pDataManager && !overflow && count > 0); + generateButton->setEnabled(m_pDataManager && !overflow && count > 0 && !m_calculationRunning); } bool nmWxSensitive::saveDataFromUI() @@ -1386,17 +1418,120 @@ void nmWxSensitive::onTreeItemChanged(QTreeWidgetItem* item, int column) void nmWxSensitive::onGenerateClicked() { - // 本阶段只保存经过校验的采样配置,实际批量求解将在下一阶段接入。 - if (!m_pDataManager || !saveDataFromUI()) return; + if(m_calculationRunning || !m_pDataManager || !saveDataFromUI()) return; + nmDataWellBase* well = m_pDataManager->findWellByCode(m_pDataManager->getPrimaryWellCode()); + if(!well || well->getWellName().isEmpty()) { + QMessageBox::warning(this, tr("Sensitivity"), tr("The target well is unavailable.")); + return; + } + + // 气井转换器使用本窗口所属分析的配置,不能随当前活动窗口切换。 + if(m_pDataManager->getSolverModelType() == SMT_Gas_VariablePvt) { + iSubWndFitting* fitting = nullptr; + QMap::const_iterator it = + nmDataAnalyzeManager::s_mapDataAnalManager.constBegin(); + for(; it != nmDataAnalyzeManager::s_mapDataAnalManager.constEnd(); ++it) { + if(it.value() == m_pDataManager) { fitting = it.key(); break; } + } + iAnalRun* analRun = fitting ? fitting->getAnalRun() : nullptr; + if(!analRun || !analRun->configPsAbouts(true, fitting->getModelOption(), + false, fitting->getAllWxPtr())) { + QMessageBox::warning(this, tr("Sensitivity"), + tr("Gas pseudo-pressure data is unavailable or invalid.")); + return; + } + } + + QString error; + QScopedPointer task; + try { + // 在管理器所属的界面线程捕获基准并复制一次,后台任务不持有管理器。 + nmCalculationDllPebiSolverTask source(QString(), m_pDataManager, well->getWellName()); + task.reset(source.createSensitivityBatch(m_sensitiveData, &error)); + } catch(const std::bad_alloc&) { + error = tr("Insufficient memory to prepare solver input."); + } + if(!task) { + QMessageBox::warning(this, tr("Sensitivity"), error); + return; + } + m_pDataManager->updateSensitiveData(m_sensitiveData); - accept(); + // 新任务准备成功后才替换上次临时结果,准备失败仍可保留旧结果。 + delete m_pSensitivityTask; + m_pSensitivityTask = task.take(); + m_pSensitivityTask->setParent(this); + // 进度框和停止按钮由主窗口统一管理;接管失败时不启动本次任务。 + emit sigSensitivityTaskReady(this, m_pSensitivityTask); + if(!m_calculationRunning) { + delete m_pSensitivityTask; + m_pSensitivityTask = nullptr; + } +} + +void nmWxSensitive::startSensitivityCalculation() +{ + // 读取和清理必须等 QThread 真正结束,不能由 run() 内的提前通知触发。 + connect(m_pSensitivityTask, SIGNAL(finished()), + this, SLOT(onSensitivityFinished()), Qt::QueuedConnection); + setCalculationRunning(true); + // 仅关闭参数界面;控制器及任务仍存活,结束后继续保留临时计算结果。 + hide(); + m_pSensitivityTask->start(); } void nmWxSensitive::onCancelClicked() { + if(m_calculationRunning && m_pSensitivityTask) { + // 关闭或 Esc 也走主窗口现有停止路径,保持进度框的等待状态一致。 + emit sigSensitivityCancelRequested(); + return; + } reject(); } +void nmWxSensitive::setCalculationRunning(bool running) +{ + // 计算过程中固定采样输入,仅保留停止操作;结束后恢复编辑与再次生成。 + m_calculationRunning = running; + calculationTypeCombo->setEnabled(!running); + variablesTree->setEnabled(!running); + detailStack->setEnabled(!running); + previewButton->setEnabled(!running); + cancelButton->setEnabled(!running); + if(running) { + generateButton->setEnabled(false); + } else { + updateModelCount(); + } +} + +void nmWxSensitive::onSensitivityFinished() +{ + if(sender() != m_pSensitivityTask) return; + m_pSensitivityTask->wait(); + // 线程结束后保留结果并恢复编辑,正常完成或停止不再弹出统计提示。 + setCalculationRunning(false); + const std::vector& results = + m_pSensitivityTask->getSensitivityResults(); + QStringList errors; + for(size_t i = 0; i < results.size() && errors.size() < 5; ++i) { + const nmCalculationDllPebiSolverTask::SensitivityRunResult& result = results[i]; + if(!result.m_bSucceeded && !result.m_bCancelled) + errors.append(tr("Run %1: %2").arg(result.m_nRunIndex + 1).arg(result.m_sFailureReason)); + } + const QString fatalError = m_pSensitivityTask->getFailureReason(); + // 实际计算错误仍需提示原因,取消组不作为错误报告。 + if(fatalError.isEmpty() && errors.isEmpty()) return; + QString message = fatalError; + if(!errors.isEmpty()) { + if(!message.isEmpty()) message += "\n\n"; + message += errors.join("\n"); + } + QWidget* ownerWindow = parentWidget() ? parentWidget()->window() : nullptr; + QMessageBox::warning(ownerWindow, tr("Sensitivity"), message); +} + void nmWxSensitive::onCalculationTypeChanged(int index) { Q_UNUSED(index);