工业软件架构 - 事件驱动 - 4
在一些复杂的系统中,任务之间可能存在相互影响的情况,比如任务 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_OBJECT
public:
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_OBJECT
public:
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();
// 启动任务A
pool->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_OBJECT
public:
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_OBJECT
public:
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 来保护共享资源,并协调任务的启动和完成。
- 灵活性: 该架构允许轻松定义和调整任务之间的依赖关系,适应各种复杂的任务调度需求。