refactor(nmNum): 预测计算复用统一进度框并在生成后隐藏输入窗口

feature/Sensitivity-20261012
lvjunjie 3 days ago
parent 280c995d0f
commit b9cb0431c1

@ -20,6 +20,7 @@ class nmDataWellBase;
class nmDataAnalyzeManager;
class nmCalculationDllPebiSolverTask;
class nmWxSensitive;
class nmWxForecast;
class iSubWndFitting;
class QWidget;
class QObject;
@ -166,8 +167,9 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit {
void onSolverCancelRequested();
/** @brief 接管敏感性批量任务,使用当前窗口的普通求解进度框。 */
void onSensitivityTaskReady(nmWxSensitive* dialog, nmCalculationDllPebiSolverTask* task);
void onSensitivityTaskFinished();
void onSensitivityTaskDestroyed();
void onForecastTaskReady(nmWxForecast* dialog, nmCalculationDllPebiSolverTask* task);
void onAuxiliarySolverTaskFinished();
void onAuxiliarySolverTaskDestroyed();
/** @brief 在框架已有的敏感性子窗口叠加显示本分析的双对数结果。 */
void onShowSensitiveResults();
// 预测结果复用所属分析内的 MDI 子窗口,模型变化时移除旧结果。
@ -305,8 +307,10 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit {
bool waitForPebiGridGeneration();
/** @brief 取消当前求解上下文对建网完成信号的订阅。 */
void disconnectPebiGridGenerationWait();
/** @brief 普通求解和敏感性分析共用同一个进度框实例及初始化路径。 */
/** @brief 普通求解、敏感性分析和预测共用进度框及初始化路径。 */
void showSolverProgress();
// 敏感性与预测共用进度和停止处理,结果仍由各自参数窗口接收。
bool prepareAuxiliarySolverTask(QWidget* dialog, nmCalculationDllPebiSolverTask* task);
// 绘图相关
nmGuiPlot* m_pWxPlot;
@ -330,8 +334,8 @@ private:
QProgressDialog* m_pProgressDlg;
nmCalculationDllPebiSolverTask* m_pSolverTask; ///< 当前窗口持有的 DLL 求解线程任务。
// 批量任务仍由敏感性窗口持有,这里只绑定进度和停止操作。
QPointer<nmCalculationDllPebiSolverTask> m_pSensitivityTask;
// 敏感性和预测任务仍由参数窗口持有,这里只绑定进度和停止操作。
QPointer<nmCalculationDllPebiSolverTask> m_pAuxiliarySolverTask;
/** @brief 启动求解时捕获的所属数据管理器,防止完成时活动窗口已经切换。 */
QPointer<nmDataAnalyzeManager> m_pSolverDataManager;
/** @brief 启动求解时捕获的拟合窗口,只把结果合并回该窗口。 */

@ -2,6 +2,7 @@
#define NMWXFORECAST_H
#include "iDlgBase.h"
#include "nmSubWxs_global.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
@ -15,20 +16,23 @@
#include <QComboBox>
#include <QPointer>
#include "nmDataForecast.h"
#include "nmGUIComponentLineEdit.h"
#include "nmDataAnalyzeManager.h"
class nmCalculationDllPebiSolverTask;
class QProgressDialog;
class nmGUIComponentLineEdit;
class nmWxForecast : public iDlgBase
class NM_SUB_WXS_EXPORT nmWxForecast : public iDlgBase
{
Q_OBJECT
public:
explicit nmWxForecast(QWidget *parent = 0, nmDataAnalyzeManager* manager = 0);
~nmWxForecast();
// 主窗口接管共享进度框后才启动计算。
void startForecastCalculation();
signals:
void sigForecastTaskReady(nmWxForecast* dialog, nmCalculationDllPebiSolverTask* task);
void sigForecastCancelRequested();
// 计算完成后由所属分析主界面承载结果,输入窗口不持有结果窗口。
void sigForecastReady(const QString& wellName, const QVector<double>& times,
const QVector<double>& pressures, const QVector<double>& durations,
@ -37,6 +41,9 @@ signals:
public slots:
void reject();
protected:
void closeEvent(QCloseEvent* event);
private slots:
// Forecast option slots
void onConstantPressureSelected();
@ -89,7 +96,7 @@ private:
nmDataForecast m_forecastData;
QPointer<nmDataAnalyzeManager> m_manager; ///< 固定所属分析窗口,不跟随全局当前窗口。
nmCalculationDllPebiSolverTask* m_task;
QProgressDialog* m_progress;
bool m_calculationRunning;
QString m_sourceSnapshotId;
QString m_wellName;
QVector<double> m_durations; ///< 本次计算的分段时长(hr)。

@ -22,6 +22,7 @@ class nmDataDiagnostic;
class nmDataReservoir;
class nmDataAnalyzeManager;
class nmWxSensitive;
class nmWxForecast;
class nmCalculationDllPebiSolverTask;
/**
@ -70,6 +71,8 @@ public:
void activateCurrentInstance();
signals:
// 预测任务交给所属主窗口,复用普通求解进度框。
void sigForecastTaskReady(nmWxForecast* dialog, nmCalculationDllPebiSolverTask* task);
// 将预测结果转交给本面板所属的分析主界面。
void sigForecastReady(const QString& wellName, const QVector<double>& times,
const QVector<double>& pressures, const QVector<double>& durations,

@ -85,6 +85,7 @@
#include "nmWxParaPropertyPebi.h"
#include "nmWxNumericalDesign.h"
#include "nmWxSensitive.h"
#include "nmWxForecast.h"
#include "nmWxReservoirProperties.h"
@ -257,13 +258,13 @@ 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_pAuxiliarySolverTask.isNull()) {
disconnect(m_pAuxiliarySolverTask.data(), nullptr, this, nullptr);
m_pAuxiliarySolverTask->requestCancel();
m_pAuxiliarySolverTask->wait();
if(s_pRunningSolverTask == m_pAuxiliarySolverTask) s_pRunningSolverTask = nullptr;
m_pAuxiliarySolverTask = nullptr;
}
if (m_pProgressDlg) {
delete m_pProgressDlg;
@ -1846,22 +1847,41 @@ void nmSubWndMain::showSolverProgress()
}
void nmSubWndMain::onSensitivityTaskReady(
nmWxSensitive* dialog, nmCalculationDllPebiSolverTask* task)
bool nmSubWndMain::prepareAuxiliarySolverTask(
QWidget* dialog, nmCalculationDllPebiSolverTask* task)
{
// 与普通求解共用防重入状态,避免两个任务同时写同一进度框。
if(!s_pPendingSolverWindow.isNull() || !s_pRunningSolverTask.isNull() ||
m_bSolverResultCompletionPending) {
QMessageBox::information(dialog, tr("solver error"), tr("task is running!"));
return;
return false;
}
m_pSensitivityTask = task;
m_pAuxiliarySolverTask = 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(task, SIGNAL(finished()), this, SLOT(onAuxiliarySolverTaskFinished()), Qt::QueuedConnection);
connect(task, SIGNAL(destroyed(QObject*)), this, SLOT(onAuxiliarySolverTaskDestroyed()));
showSolverProgress();
m_pProgressDlg->setLabelText(nmCalculationDllPebiSolverTask::tr("Solving model..."));
return true;
}
void nmSubWndMain::onForecastTaskReady(
nmWxForecast* dialog, nmCalculationDllPebiSolverTask* task)
{
// 预测沿用共享进度框和停止路径,计算结果不进入正式成果提交。
if(!prepareAuxiliarySolverTask(dialog, task)) return;
connect(dialog, SIGNAL(sigForecastCancelRequested()),
this, SLOT(onSolverCancelRequested()), Qt::UniqueConnection);
dialog->startForecastCalculation();
}
void nmSubWndMain::onSensitivityTaskReady(
nmWxSensitive* dialog, nmCalculationDllPebiSolverTask* task)
{
// 整个批次保持同一个滚动进度,不订阅每组求解的阶段刷新信号。
if(!prepareAuxiliarySolverTask(dialog, task)) return;
connect(dialog, SIGNAL(sigSensitivityCancelRequested()),
this, SLOT(onSolverCancelRequested()), Qt::UniqueConnection);
// 坐标标题与单位跟随求解时的双对数窗口,后来切换模型不改变已有批次。
@ -1876,27 +1896,25 @@ void nmSubWndMain::onSensitivityTaskReady(
result->m_sAxisYUnit = source->getAxisOf(1)->getUnit();
}
}
showSolverProgress();
m_pProgressDlg->setLabelText(nmCalculationDllPebiSolverTask::tr("Solving model..."));
dialog->startSensitivityCalculation();
}
void nmSubWndMain::onSensitivityTaskFinished()
void nmSubWndMain::onAuxiliarySolverTaskFinished()
{
// 完整退出线程后再释放进度框的占用,不进入普通求解的成果提交路径。
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;
if(m_pAuxiliarySolverTask.isNull() || sender() != m_pAuxiliarySolverTask.data()) return;
m_pAuxiliarySolverTask->wait();
if(s_pRunningSolverTask == m_pAuxiliarySolverTask) s_pRunningSolverTask = nullptr;
disconnect(m_pAuxiliarySolverTask.data(), nullptr, this, nullptr);
m_pAuxiliarySolverTask = nullptr;
m_bSolverCancelRequested = false;
m_pProgressDlg->hide();
}
void nmSubWndMain::onSensitivityTaskDestroyed()
void nmSubWndMain::onAuxiliarySolverTaskDestroyed()
{
// 参数面板提前销毁时,其析构函数已等待任务结束,解除共享进度框占用。
m_pSensitivityTask = nullptr;
m_pAuxiliarySolverTask = nullptr;
m_bSolverCancelRequested = false;
m_pProgressDlg->hide();
}
@ -2722,8 +2740,8 @@ void nmSubWndMain::onSolverCancelRequested()
}
// 共享停止按钮只控制当前占用进度框的任务,DLL 返回后的处理各自保持原路径。
nmCalculationDllPebiSolverTask* pRunningTask = !m_pSensitivityTask.isNull()
? m_pSensitivityTask.data() : m_pSolverTask;
nmCalculationDllPebiSolverTask* pRunningTask = !m_pAuxiliarySolverTask.isNull()
? m_pAuxiliarySolverTask.data() : m_pSolverTask;
if(pRunningTask != nullptr && pRunningTask->isRunning()) {
m_bSolverCancelRequested = true;
pRunningTask->requestCancel();
@ -3126,7 +3144,7 @@ bool nmSubWndMain::onConfirmClosing()
{
// 正常关闭入口在求解结束前保持当前成果和 DataManager 存活;应用退出等绕过
// 该确认流程的路径仍由析构函数 wait() 兜底。
if(m_bSolverStartPending || !m_pSensitivityTask.isNull() ||
if(m_bSolverStartPending || !m_pAuxiliarySolverTask.isNull() ||
(m_pSolverTask != nullptr && m_pSolverTask->isRunning())) {
QMessageBox::information(this, tr("solver error"), tr("task is running!"));
return false;

@ -481,6 +481,8 @@ void nmSubWndUtils::setupNumericalDockWidgets(nmDataAnalyzeManager* pDataManager
// 数值设计面板只发出操作请求,具体处理仍由主窗口完成。
connect(pAnalWx, SIGNAL(sigGenerateClicked()),
pSubWndMain, SLOT(onGenerateButtonClicked()));
connect(pAnalWx, SIGNAL(sigForecastTaskReady(nmWxForecast*,nmCalculationDllPebiSolverTask*)),
pSubWndMain, SLOT(onForecastTaskReady(nmWxForecast*,nmCalculationDllPebiSolverTask*)));
connect(pAnalWx, SIGNAL(sigForecastReady(QString,QVector<double>,QVector<double>,QVector<double>,QVector<double>)),
pSubWndMain, SLOT(onShowForecastResult(QString,QVector<double>,QVector<double>,QVector<double>,QVector<double>)));
connect(pAnalWx, SIGNAL(sigSensitivityTaskReady(nmWxSensitive*,nmCalculationDllPebiSolverTask*)),

@ -5,16 +5,17 @@
#include <QSizePolicy>
#include <QLabel>
#include <QMessageBox>
#include <QProgressDialog>
#include <QCloseEvent>
#include <QtCore/qnumeric.h>
#include "nmCalculationDllPebiSolverTask.h"
#include "nmGUIComponentLineEdit.h"
#include "nmPebiResultSnapshot.h"
nmWxForecast::nmWxForecast(QWidget *parent, nmDataAnalyzeManager* manager)
: iDlgBase(parent)
, m_manager(manager ? manager : nmDataAnalyzeManager::getCurrentInstance())
, m_task(NULL)
, m_progress(NULL)
, m_calculationRunning(false)
, m_initialPressureComponent(nullptr)
, m_abandonmentRateComponent(nullptr)
, m_maximumRateComponent(nullptr)
@ -1039,16 +1040,23 @@ void nmWxForecast::onGenerateClicked()
m_durations = durations;
m_rates = rates;
m_sourceSnapshotId = solved->getSnapshotId();
// 与敏感性分析一样,主窗口接管成功后才启动,忙碌时释放未启动任务。
emit sigForecastTaskReady(this, m_task);
if(!m_calculationRunning) {
delete m_task;
m_task = NULL;
}
}
void nmWxForecast::startForecastCalculation()
{
// 先连接主窗口的完成处理,再由本窗口读取结果和释放任务。
connect(m_task, SIGNAL(finished()), this, SLOT(onForecastFinished()), Qt::QueuedConnection);
m_calculationRunning = true;
m_parametersGroup->setEnabled(false);
m_generateButton->setEnabled(false);
m_progress = new QProgressDialog(tr("Calculating forecast..."), tr("Cancel"), 0, 0, this);
m_progress->setWindowTitle(tr("Forecast"));
m_progress->setWindowModality(Qt::ApplicationModal);
m_progress->setMinimumDuration(0);
m_progress->setAutoClose(false);
connect(m_progress, SIGNAL(canceled()), this, SLOT(onCancelClicked()));
connect(m_task, SIGNAL(finished()), this, SLOT(onForecastFinished()));
m_progress->show();
// 与敏感性分析一致,隐藏输入窗口,保留对象接收计算结果。
hide();
m_task->start();
}
@ -1061,16 +1069,24 @@ void nmWxForecast::onCancelClicked()
void nmWxForecast::reject()
{
// 正在求解时保留窗口,等线程结束后恢复输入,避免关闭操作阻塞界面。
if(m_task) {
m_task->requestCancel();
m_progress->setLabelText(tr("Stopping after the current solver call finishes..."));
m_progress->setCancelButton(NULL);
m_progress->show();
if(m_calculationRunning) {
emit sigForecastCancelRequested();
return;
}
iDlgBase::reject();
}
void nmWxForecast::closeEvent(QCloseEvent* event)
{
// 非模态进度框允许操作输入窗口,点关闭时同样请求停止并等待线程退出。
if(m_calculationRunning) {
emit sigForecastCancelRequested();
event->ignore();
return;
}
iDlgBase::closeEvent(event);
}
bool nmWxForecast::readRateSchedule(QVector<double>& durations, QVector<double>& rates, QString& error) const
{
// 求解器统一使用小时及标准体积产量,完整空行跳过,半空行必须补全。
@ -1107,7 +1123,7 @@ void nmWxForecast::onForecastFinished()
{
// finished 后才读取及销毁任务;输入变化或模型重新生成时丢弃本次预测。
m_task->wait();
const bool cancelled = m_task->wasCancelled() || m_progress->wasCanceled();
const bool cancelled = m_task->wasCancelled();
const bool current = m_manager && m_manager->isPebiResultSnapshotCurrent() &&
!m_task->hasInputChanged(m_manager) &&
m_manager->getPebiResultSnapshot()->getSnapshotId() == m_sourceSnapshotId;
@ -1116,8 +1132,7 @@ void nmWxForecast::onForecastFinished()
const QVector<QVector<double> > pressure = m_task->getRateForecastPressure();
delete m_task;
m_task = NULL;
delete m_progress;
m_progress = NULL;
m_calculationRunning = false;
m_parametersGroup->setEnabled(true);
m_generateButton->setEnabled(true);
if(cancelled) return;
@ -1131,7 +1146,7 @@ void nmWxForecast::onForecastFinished()
void nmWxForecast::onModelChanged()
{
// 模型或输入改变时停止本次计算;已显示的结果由所属主界面清理。
if(m_task) m_task->requestCancel();
if(m_calculationRunning) emit sigForecastCancelRequested();
}
void nmWxForecast::onPressureCellChanged(int row, int column)

@ -1449,6 +1449,8 @@ void nmWxNumericalDesign::onForecastClicked()
updateSensitiveButtonEnabled();
if(!m_pForecastBtn->isEnabled()) return;
nmWxForecast* dlg = new nmWxForecast(this, m_pDataManager);
connect(dlg, SIGNAL(sigForecastTaskReady(nmWxForecast*,nmCalculationDllPebiSolverTask*)),
this, SIGNAL(sigForecastTaskReady(nmWxForecast*,nmCalculationDllPebiSolverTask*)));
connect(dlg, SIGNAL(sigForecastReady(QString,QVector<double>,QVector<double>,QVector<double>,QVector<double>)),
this, SIGNAL(sigForecastReady(QString,QVector<double>,QVector<double>,QVector<double>,QVector<double>)));
dlg->show();

Loading…
Cancel
Save