【问题标题】:Pattern/architecture to select and execute tasks depending on the incoming command根据传入命令选择和执行任务的模式/架构
【发布时间】:2018-05-07 12:19:41
【问题描述】:

通常,我会通过以下方式解决此问题:

task = _taskProvider.getNextFromQueue();

if (task.id == TaskExecutor.TasksEnum.One) {
    _taskExecutor.executeTaskOne();
    return;
} else if (task.id == TaskExecutor.TasksEnum.Two) {
    _taskExecutor.executeTaskTwo();
    return;
} else if (task.id == TaskExecutor.TasksEnum.Three) {
    _taskExecutor.executeTaskThree();
    return;
}
//and so on...

如果需要,我可以将 if-else 切换为 switch。 我确实相信存在更好的方法来实现类似的代码。 想到的一件事是使用表(映射)来存储任务 ID 和指向相应函数的指针,但我不确定它是否有用并提供足够的性能。

我关心的另一件事是通知机制。当TaskProviderTaskExecutor 是在不同线程上运行的两个独立实体时,就会出现此问题。如果几个动作需要一些时间来完成执行,情况会变得更糟,我需要创建一个逻辑来让调用者等待结果。

例如,如果executeTaskTwo() 需要一些时间才能完成,我将在后台线程中执行它,这意味着executeTaskTwo() 将几乎立即返回,而任务本身仍将执行。但是一旦结束,我如何通知调用者它的完成?

我猜该解决方案涉及大量修改,也许是一些事件库(我以前从未使用过这些)。我需要一个起点。我想存在某种模式,但我不知道到底是哪一种。

更新 1:

有关一般架构和问题的更多信息 共有三个实体:

  • Hardware - 管理硬件​​信号(输入、输出)。只关心硬件,不问为什么?
  • Network - 管理与设备的远程连接。只关心数据传输。同样,它并不关心数据代表什么。
  • Controller - 控制实际设备的任务/算法(此设备的功能)。这是设备的大脑。

每个实体都在自己的线程上运行。有时,发生了一个事件,一个实体需要将此事件发送给另一个实体(或所有实体)。例如,来自network 的命令会打开某个由硬件管理的 LED,或者,如果未能打开此 LED,应同时向network(通知远程客户端)和controller(执行紧急停止)发出信号。

现在每个实体(类)都有一组函数(方法),其他实体在这个实体做某事时会调用这些函数(方法)。如果调用的函数需要时间来执行,那么调用者实体就会被阻塞,这是不好的。产生后台线程来处理每个命令(或事件)也感觉不对。 目前每个实体都有自己的命令队列并按顺序执行其命令 (execute(queue.getNext())),但由于有些命令执行速度很快,而另一些则需要时间和资源,因此效果不佳。

【问题讨论】:

  • 是的,我肯定会在这里使用地图。例如TasksEnum.ThreeexecuteTaskThree 之间的映射可以正常工作。如果您有大量命令,则地图查找实际上可能比 if...elses 更快,因为它不需要对每个命令进行线性搜索。
  • 你可以传递一个指向函数的指针。

标签: c++ multithreading events design-patterns notifications


【解决方案1】:

我确实相信存在更好的方法来实现类似的代码。

switch 语句是你能得到的最好的。它会(通常)在您的机器代码中生成一个跳转表,直接将程序计数器发送到该位置以继续执行。

使用map(红黑树)来存储idspointers to functions,不如在switch 中那样有效,并且可能会阻止内联优化。

我会在后台线程中执行它,这意味着 executeTaskTwo() 几乎会立即返回。

我对多线程不是很了解,但你为什么要早点回来?

一个线程的指向不就是join结束后其余的线程吗?

如果你的意思是主线程继续做其他事情,那么你可以有一个std::atomic<bool> 告诉你进程是否已经完成。如果只有一个线程写入(工作线程),而其他线程读取(主线程),则行为已明确。

如果您不想存储一堆布尔值并且只想知道某个进程是否仍在运行,您可以有一个计数器,您可以递增并且随着线程的开始和结束而递减

【讨论】:

  • 我不知道如何更准确地描述,但想法如下:简单的解决方案在任务量小的时候是好的,但有时这些 switch 语句会变得非常大并且难以阅读。如果使用std::atomic<bool> 标志方式,则意味着每个任务都需要相应的标志和if(flag) 语句,这使得代码量非常难以维护。
  • @CorellianAle 在我看来,switch 语句非常易读,即使对于长序列也是如此。特别是如果您将中断放在函数调用的右侧,但这是您的调用。查看SO question 了解如何检查线程是否仍在运行。
【解决方案2】:

您可以将 Task 作为基类并让 `getNextFromQueue() 返回一个派生自它的具体类吗?这是一个简单的代码,可以提供想法/

class Task
{
public:
void execute() = 0;

}

class TaskOne : public Task
{
public:
void execute()
  {
     // execute the task here
  }
}

所以您的代码将如下所示:

Task * task = _taskProvider.getNextFromQueue();

if (task)
   task->execute()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多