工业软件架构4:(QT和C++实现)
工业软件架构 - 事件驱动 - 4
- 1. 任务依赖关系的管理
- 2. 任务依赖管理器
- 3. 任务类的定义
- 4. 任务的执行
- 5. 运行原理
- 6. 忽略型任务
- 6.1 任务状态检查机制
- 任务状态管理器
- 任务类定义
- 主程序
- 特点
- 优点
- 缺点
- 适用场景
- 6.2 任务队列过滤机制
- 任务队列过滤器
- 任务类定义
- 主程序
- 特点
- 优点
- 缺点
- 适用场景
- 6.3 基于信号与槽的任务忽略
- 任务控制器
- 任务类定义
- 主程序
- 特点
- 优点
- 缺点
- 适用场景
- 对比总结
- 选择建议
- 7. 总结
在一些复杂的系统中,任务之间可能存在相互影响的情况,比如任务 A 在运行时,任务 B 不能运行,或者任务 C 只能在任务 A 或 B 完成后才能运行。为了处理这些情况,需要设计一种机制来协调任务之间的关系,确保它们按照预期的顺序和条件执行。
1. 任务依赖关系的管理
要处理这种情况,我们需要引入任务依赖关系的概念。可以通过以下几种方式来管理任务之间的依赖关系:
- 任务状态管理: 维护任务的状态(如正在运行、已完成、等待中),并根据状态决定是否可以执行某个任务。
- 互斥锁(QMutex): 使用 QMutex 来防止某些任务在特定条件下同时运行。
- 信号与槽机制: 使用信号与槽机制,在任务完成后通知其他任务是否可以启动。
2. 任务依赖管理器
创建一个任务依赖管理器(TaskDependencyManager)来协调任务之间的依赖关系。这个管理器将负责跟踪任务状态、管理互斥锁,以及协调任务的执行顺序。
#include <QObject>
#include <QMutex>
#include <QWaitCondition>
#include <QMap>
#include <QRunnable>
#include <QThreadPool>
#include <QDebug>class TaskDependencyManager : public QObject
{Q_OBJECTpublic:enum TaskState {Waiting,Running,Completed};void registerTask(const QString &taskName){QMutexLocker locker(&mutex);taskStates[taskName] = Waiting;}void startTask(const QString &taskName, QRunnable *task){QMutexLocker locker(&mutex);if (canStartTask(taskName)) {taskStates[taskName] = Running;QThreadPool::globalInstance()->start(task);} else {qDebug() << "Task" << taskName << "is waiting for dependencies.";}}void completeTask(const QString &taskName){QMutexLocker locker(&mutex);taskStates[taskName] = Completed;condition.wakeAll(); // 唤醒所有等待的任务qDebug() << "Task" << taskName << "completed.";}bool canStartTask(const QString &taskName) {// 这里可以根据实际情况来定义依赖规则if (taskStates.contains("A") && taskStates["A"] == Running && taskName == "B") {return false; // 如果任务A正在运行,则任务B不能启动}return true;}void waitForTaskCompletion(const QString &taskName){QMutexLocker locker(&mutex);while (taskStates[taskName] != Completed) {condition.wait(&mutex);}}private:QMutex mutex;QWaitCondition condition;QMap<QString, TaskState> taskStates;
};
3. 任务类的定义
定义任务类,任务在执行前需要先检查依赖管理器,以决定是否可以开始执行。
class TaskA : public QRunnable
{
public:TaskA(TaskDependencyManager *manager): manager(manager) {}void run() override {manager->startTask("A", this);qDebug() << "Task A started";QThread::sleep(2); // 模拟任务A的耗时操作qDebug() << "Task A finished";manager->completeTask("A");}private:TaskDependencyManager *manager;
};class TaskB : public QRunnable
{
public:TaskB(TaskDependencyManager *manager): manager(manager) {}void run() override {manager->waitForTaskCompletion("A"); // 等待任务A完成manager->startTask("B", this);qDebug() << "Task B started";QThread::sleep(2); // 模拟任务B的耗时操作qDebug() << "Task B finished";manager->completeTask("B");}private:TaskDependencyManager *manager;
};class TaskC : public QRunnable{
public:TaskC(TaskDependencyManager *manager): manager(manager) {}void run() override {manager->waitForTaskCompletion("A"); // 等待任务A完成manager->waitForTaskCompletion("B"); // 等待任务B完成manager->startTask("C", this);qDebug() << "Task C started";QThread::sleep(2); // 模拟任务C的耗时操作qDebug() << "Task C finished";manager->completeTask("C");}private:TaskDependencyManager *manager;
};
4. 任务的执行
在主程序中,任务依赖管理器会负责启动任务,并根据任务之间的依赖关系决定任务的执行顺序。
int main(int argc, char *argv[])
{QCoreApplication app(argc, argv);TaskDependencyManager manager;manager.registerTask("A");manager.registerTask("B");manager.registerTask("C");TaskA *taskA = new TaskA(&manager);TaskB *taskB = new TaskB(&manager);TaskC *taskC = new TaskC(&manager);QThreadPool::globalInstance()->start(taskA);QThreadPool::globalInstance()->start(taskB);QThreadPool::globalInstance()->start(taskC);return app.exec();
}
5. 运行原理
- Task A: 首先启动并运行,因为它不依赖于其他任务的状态。
- Task B: 在任务 A 运行时,任务 B 将等待任务 A 完成后再启动。
- Task C: 在任务 A 和任务 B 都完成后,任务 C 才会启动。
6. 忽略型任务
在某些情况下,我们需要设计一种机制来忽略特定的任务。例如,当任务 A 正在运行时,如果任务 B 被触发,任务 B 需要被忽略而不执行。这种需求可以通过几种方式实现,具体取决于任务的触发和调度机制。
6.1 任务状态检查机制
一种常见的方法是使用任务状态检查机制。在这种机制中,任务在启动时会检查某些条件(如其他任务的状态),如果条件不满足,就会选择不执行任务。任务状态可以通过全局或共享的变量来跟踪,并在任务之间共享。
任务状态管理器
首先,我们创建一个任务状态管理器,用于跟踪任务的状态(如正在运行或空闲)。
#include <QObject>
#include <QMutex>
#include <QDebug>class TaskStatusManager : public QObject
{Q_OBJECTpublic:enum TaskState {Idle,Running};void setTaskState(const QString &taskName, TaskState state){QMutexLocker locker(&mutex);taskStates[taskName] = state;}TaskState getTaskState(const QString &taskName) {QMutexLocker locker(&mutex);return taskStates.value(taskName, Idle);}private:QMutex mutex;QMap<QString, TaskState> taskStates;
};
任务类定义
然后,我们定义两个任务 A 和 B。任务 A 在运行时,任务 B 如果被触发,将会被忽略。
class TaskA : public QRunnable{
public:TaskA(TaskStatusManager *statusManager): statusManager(statusManager) {}void run() override {statusManager->setTaskState("A", TaskStatusManager::Running);qDebug() << "Task A started";QThread::sleep(5); // 模拟任务A的耗时操作qDebug() << "Task A finished";statusManager->setTaskState("A", TaskStatusManager::Idle);}private:TaskStatusManager *statusManager;
};class TaskB : public QRunnable
{
public:TaskB(TaskStatusManager *statusManager): statusManager(statusManager) {}void run() override{// 检查任务A的状态if (statusManager->getTaskState("A") == TaskStatusManager::Running){qDebug() << "Task B ignored because Task A is running";return; // 忽略任务B}qDebug() << "Task B started";QThread::sleep(2); // 模拟任务B的耗时操作qDebug() << "Task B finished";}private:TaskStatusManager *statusManager;
};
主程序
最后,在主程序中,我们初始化任务状态管理器并启动任务 A 和任务 B。任务 B 将会在任务 A 运行期间被忽略。
#include <QCoreApplication>
#include <QThreadPool>int main(int argc, char *argv[])
{QCoreApplication app(argc, argv);TaskStatusManager statusManager;TaskA *taskA = new TaskA(&statusManager);TaskB *taskB = new TaskB(&statusManager);QThreadPool *pool = QThreadPool::globalInstance();// 启动任务Apool->start(taskA);// 模拟任务B在任务A运行期间被触发QThread::sleep(1); // 等待任务A开始运行pool->start(taskB);return app.exec();
}
特点
- 任务内部检查: 在任务运行时检查其他任务的状态决定是否继续执行。任务自己负责判断是否需要执行或忽略。
- 动态灵活: 任务在执行时动态判断状态,适合任务在执行过程中需要实时判断条件的场景。
- 独立性强: 每个任务独立进行状态检查,适合任务数量较少或任务逻辑相对简单的场景。
优点
- 易于实现: 只需在任务的开头添加状态检查逻辑即可,无需额外的队列或复杂的事件系统。
- 独立性: 每个任务都可以独立决定是否继续执行,降低了任务之间的耦合性。
- 实时判断: 适合需要在任务运行时动态判断是否需要继续执行的场景。
缺点
- 管理复杂性: 当任务数量增多或依赖关系复杂时,每个任务都需要包含检查逻辑,管理和维护的复杂性增加。
- 可能的资源浪费: 如果任务被启动后才发现不应执行(如在开头状态检查后发现),可能会造成资源浪费(如线程启动的开销)。
适用场景
- 适用于任务数量较少、任务逻辑相对简单的场景。
- 适用于任务在运行过程中需要实时判断是否继续执行的情况。
6.2 任务队列过滤机制
另一种方法是使用任务队列过滤机制。在任务进入队列之前或从队列中取出时,检查其是否应该被忽略。如果某个任务正在运行,而另一个任务被触发但需要忽略,则该任务不会被添加到执行队列中。
任务队列过滤器
首先,我们定义一个任务队列过滤器,用于在任务进入队列之前检查其执行条件。
#include <QObject>
#include <QQueue>
#include <QMutex>
#include <QRunnable>
#include <QThreadPool>
#include <QDebug>class TaskQueueFilter : public QObject{Q_OBJECTpublic:void addTask(QRunnable *task, const QString &taskName) {QMutexLocker locker(&mutex);// 检查任务是否可以执行if (taskName == "B" && isTaskRunning("A")){qDebug() << "Task" << taskName << "is ignored because Task A is running.";delete task; // 忽略任务B,不将其添加到队列中return;}// 添加任务到队列并执行queue.enqueue(task);QThreadPool::globalInstance()->start(task);qDebug() << "Task" << taskName << "is added to the queue.";}void setTaskRunning(const QString &taskName, bool running) {QMutexLocker locker(&mutex);runningTasks[taskName] = running;}private:bool isTaskRunning(const QString &taskName) {return runningTasks.value(taskName, false);}QQueue<QRunnable*> queue;QMap<QString, bool> runningTasks;QMutex mutex;
};
任务类定义
我们定义两个任务 A 和 B。任务 A 运行时,任务 B 被触发将被忽略。
class TaskA : public QRunnable{
public:TaskA(TaskQueueFilter *filter): filter(filter) {}void run() override{filter->setTaskRunning("A", true);qDebug() << "Task A started";QThread::sleep(5); // 模拟任务A的耗时操作qDebug() << "Task A finished";filter->setTaskRunning("A", false);}private:TaskQueueFilter *filter;
};class TaskB : public QRunnable{
public:TaskB(TaskQueueFilter *filter): filter(filter) {}void run() override{qDebug() << "Task B started";QThread::sleep(2); // 模拟任务B的耗时操作qDebug() << "Task B finished";}private:TaskQueueFilter *filter;
};
主程序
在主程序中,我们初始化任务过滤器并启动任务 A 和任务 B。任务 B 会在任务 A 运行期间被忽略。
#include <QCoreApplication>
#include <QThreadPool>int main(int argc, char *argv[])
{QCoreApplication app(argc, argv);TaskQueueFilter filter;TaskA *taskA = new TaskA(&filter);TaskB *taskB = new TaskB(&filter);filter.addTask(taskA, "A");// 模拟任务B在任务A运行期间被触发QThread::sleep(1);filter.addTask(taskB, "B");return app.exec();
}
特点
- 任务提交前检查: 在任务被添加到队列之前,检查其是否满足执行条件。只有满足条件的任务才会被添加到执行队列中。
- 预防性: 在任务进入队列前就进行过滤,防止不必要的任务进入执行队列。
- 队列集中管理: 任务的添加和执行由队列统一管理,适合集中化任务调度的场景。
优点
- 资源节约: 不会启动不必要的任务,从而节约系统资源,避免浪费。
- 集中控制: 所有任务的状态检查和过滤都集中在任务队列中,易于管理和扩展。
- 效率高: 任务在进入队列前就被过滤掉,避免了不必要的执行准备工作。
缺点
- 灵活性较低: 由于检查是在任务提交前进行的,如果任务状态在提交后发生变化,任务可能无法响应这些变化。
- 需要额外的队列管理逻辑: 需要维护一个任务队列和状态检查的逻辑,增加了系统复杂性。
适用场景
- 适用于任务量大且需要集中调度和管理的场景。
- 适用于任务状态在任务提交前确定,不需要在任务执行过程中动态检查的场景。
6.3 基于信号与槽的任务忽略
使用信号与槽机制,可以在任务被触发时动态检查并决定是否执行任务。
任务控制器
任务控制器负责接收任务的启动信号,并决定是否忽略任务。
#include <QObject>
#include <QRunnable>
#include <QThreadPool>
#include <QDebug>class TaskController : public QObject
{Q_OBJECTpublic:void startTask(const QString &taskName) {if (taskName == "B" && isTaskARunning){qDebug() << "Task B is ignored because Task A is running.";return;}if (taskName == "A") {isTaskARunning = true;}// 创建任务并启动QRunnable *task = createTask(taskName);if (task){QThreadPool::globalInstance()->start(task);qDebug() << "Task" << taskName << "started.";}}void setTaskAState(bool running){isTaskARunning = running;}signals:void taskFinished(const QString &taskName);private:QRunnable* createTask(const QString &taskName){if (taskName == "A") {return new TaskA(this);} else if (taskName == "B") {return new TaskB(this);}return nullptr;}bool isTaskARunning = false;
};
任务类定义
任务 A 完成后会发送一个信号通知任务控制器更新状态。
class TaskA : public QRunnable{
public:TaskA(TaskController *controller): controller(controller) {}void run() override {qDebug() << "Task A started";QThread::sleep(5); // 模拟任务A的耗时操作qDebug() << "Task A finished";controller->setTaskAState(false);emit controller->taskFinished("A");}private:TaskController *controller;
};class TaskB : public QRunnable{
public:TaskB(TaskController *controller): controller(controller) {}void run() override{qDebug() << "Task B started";QThread::sleep(2); // 模拟任务B的耗时操作qDebug() << "Task B finished";}
private:TaskController *controller;
};
主程序
在主程序中,我们连接信号与槽,并启动任务 A 和任务 B。任务 B 会在任务 A 运行时被忽略。
#include <QCoreApplication>int main(int argc, char *argv[]){QCoreApplication app(argc, argv);TaskController controller;QObject::connect(&controller, &TaskController::taskFinished, [&controller](const QString &taskName) {if (taskName == "A") {controller.setTaskAState(false);}});controller.startTask("A");// 模拟任务B在任务A运行期间被触发QThread::sleep(1);controller.startTask("B");return app.exec();
}
特点
- 事件驱动: 基于信号与槽机制,任务的执行和忽略由信号触发的逻辑决定。
- 动态响应: 当任务状态或条件发生变化时,可以通过信号立即触发相应的处理逻辑。
- 松耦合: 信号与槽机制使得任务之间的依赖关系松耦合,易于扩展和修改。
优点
- 实时响应: 当任务状态或条件发生变化时,可以立即通过信号通知其他任务,实现实时控制。
- 灵活性高: 信号与槽机制使得任务可以灵活响应不同的事件或条件,适应性强。
- 易于扩展: 添加新任务或修改任务逻辑时,只需调整信号与槽的连接方式,方便扩展和维护。
缺点
- 复杂性增加: 需要管理信号与槽的连接关系,增加了系统的复杂性,尤其是在大型系统中,信号与槽的管理可能变得复杂。
- 调试难度较大: 由于信号与槽机制是事件驱动的,调试和追踪任务的执行路径可能变得困难。
适用场景
- 适用于任务之间存在复杂依赖关系或需要动态响应状态变化的场景。
- 适用于系统中任务数量较多且需要灵活管理和扩展的场景。
对比总结
机制 | 特点 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
任务状态检查机制 | 每个任务在执行时独立检查状态 | 易于实现、独立性强、实时判断 | 随任务数量增加管理复杂性增加、可能资源浪费 | 任务数量少、逻辑简单 |
任务队列过滤机制 | 任务提交前进行检查,未满足条件任务被过滤 | 资源节约、集中控制、效率高 | 灵活性低、需要额外队列管理逻辑 | 任务量大、集中调度 |
基于信号与槽的任务忽略 | 通过信号与槽机制动态决定任务执行或忽略 | 实时响应、灵活性高、易于扩展 | 系统复杂性增加、调试难度大 | 复杂依赖关系、任务多且需要灵活管理 |
选择建议
-
任务状态检查机制: 如果你的系统中任务相对独立,并且任务数量较少,使用任务状态检查机制更为简单和直观。它特别适合需要在任务运行过程中动态判断的情况。
-
任务队列过滤机制: 如果你的系统有大量任务需要集中管理,并且任务状态在提交前可以确定,任务队列过滤机制更为高效。它能够在任务进入队列前进行过滤,节省资源。
-
基于信号与槽的任务忽略: 如果你的系统任务之间有复杂的依赖关系,或者需要灵活响应不同的事件或条件,使用基于信号与槽的任务忽略机制是最合适的。这种机制的实时响应能力和扩展性是它的最大优势。
7. 总结
通过引入 TaskDependencyManager 来管理任务之间的依赖关系和状态,可以确保任务按照预期的顺序执行,并防止在不适当的情况下启动某些任务。这种设计特别适合处理复杂的任务调度场景,例如需要确保多个任务之间的顺序性或互斥性的应用程序。
- 任务状态管理: 通过任务状态管理,确保任务在合适的时间点启动或完成。
- 互斥锁和条件变量: 使用 QMutex 和 QWaitCondition 来保护共享资源,并协调任务的启动和完成。
- 灵活性: 该架构允许轻松定义和调整任务之间的依赖关系,适应各种复杂的任务调度需求。