使用 SheduleExecutorService 将任务提交给 ExecutorService

Submit a task to an ExecutorService using a SheduleExecutorService

我正在开发一个 JavaFX 应用程序,用于从串行设备读取数据并在新设备连接到计算机时显示通知。

我有一个任务 DeviceDetectorTask 扫描所有端口并在连接新设备时创建一个事件。此任务必须每 3 秒提交一次。

当检测到设备时,用户可以按一个按钮来读取其中包含的所有数据。这是由另一个任务 ReadDeviceTask 执行的。此时,当 ReadDeviceTask 为 运行 时,不应执行扫描操作(我无法同时读取和扫描一个端口)。所以一次只能运行两个任务中的一个。

我的实际解决方案是:

public class DeviceTaskQueue {
    private ExecutorService executorService = Executors.newSingleThreadExecutor();

    public void submit(Runnable task) {
        executorService.submit(task);
    }
}


public class ScanScheduler {
    private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

    public void start() {
        AddScanTask task = new AddScanTask();
        executor.scheduleAtFixedRate(task, 0, 3, TimeUnit.SECONDS);
    }
}


public class AddScanTask implements Runnable {
    @Autowired
    DeviceTaskQueue deviceTaskQueue;

    @Override
    public void run() {
        deviceTaskQueue.submit(new DeviceDetectorTask());
    }
}

public class ViewController {
    @Autowired
    DeviceTaskQueue deviceTaskQueue;

    @FXML
    private readDataFromDevice() {
        deviceTaskQueue.submit(new ReadDeviceTask());
    }
}

我的问题是:是否可以从 ScheduledExecutorService 调度的任务 AddScanTask 中添加一个任务到 ExecutorService?

是的,一个执行者可以 Post 任务给另一个执行者

在最后一行回答你的简单问题:

is it ok to add a task to the ExecutorService from the task AddScanTask which has been scheduled by the ScheduledExecutorService?

是的。当然,您可以从任何其他代码提交 Callable/Runnable。提交代码恰好是来自另一个执行者的 运行ning 是无关紧要的,因为来自执行者的代码 运行 仍然是“正常” Java 代码,只是 运行ning 在一个不同的线程。

这就是执行程序的全部意义所在,以方便程序员的方式处理线程的杂耍。使多线程编码更容易和更不容易出错是将这些 类 添加到 Java 的原因。请参阅 Brian Goetz 等人撰写的极其有用的书 Java Concurrency in Practice。并查看 Goetz 的其他著作。

在你的例子中,你有两个执行器,每个执行器都有自己的线程,每个执行器都执行一系列提交的任务。一种是自动(定时)提交任务,另一种是手动(任意)提交任务。每个都在彼此独立的线程上执行。对于多个内核,它们可以同时执行。

这里存在更大的问题:在您的场景中您不希望它们是独立的。您希望阅读任务阻止扫描任务。

更大的问题

您提出的问题是,当任意事件(读取)发生时,定期发生的 activity(扫描)必须停止。这意味着这两项活动必须相互协调。问题是如何协调。

信号量

当任意事件发生时,它应该升起一个标志。重复出现的 activity,当它 运行 时,应该始终检查该标志。如果升起,请等待标志降低,然后再继续扫描。 ScheduledExecutorService 就是为此而设计的,可以容忍一个任务 运行 的时间可能比预定的时间长。如果任务的一次执行 运行s 长,SES 会再次 而不是 运行,因此它不会积压执行。这正是您想要的行为。

反之亦然,如果重复出现的 activity 正在执行,它应该发出一个标志。任意事件的第一个待办事项是检查该标志。如果升高,请等待直至降低。然后继续,首先升起自己的旗帜,然后继续手头的任务(扫描)。

也许您的方案应该设计为使用单个标志而不是扫描仪,并且 reader 每个都有自己的标志。我将不得不更多地考虑它并且可能更多地了解您的情况。

此类标志的技术术语是 semaphore

很遗憾,您的评论说您无法更改扫描仪的源代码。所以你不能实现信号量和协调活动。所以我卡住了,看不到解决方案。

破解

考虑到您的冻结代码,我推荐的一种黑客解决方案是定期发生的activity(扫描)实际上并没有起作用,而是相反 post 另一个线程(另一个执行程序)上的扫描任务。其他执行者也将是 用于 post 任意 activity(读数)的同一执行者。所以有一个单一的待办事项队列,扫描和阅读作业的混合,提交给单线程执行器。单线程意味着他们按照提交的顺序一次完成一个。

我不喜欢这个 hack,因为如果任何待办事项需要很长时间,您就会开始积压。那可能是一团糟。


顺便说一句,您的示例代码中不需要 DeviceTaskQueue。直接调用ExecutorService的实例提交任务即可。这是 ExecutorService 的工作,包装它不会增加我所看到的任何价值。