并行基础结构

复杂系统的其中一个主要问题是在执行任务时保持响应状态。在可扩充的系统中,当不是用来一起运行的组件却共享相同的资源时,这种问题更为突出。org.eclipse.core.runtime.jobs 包通过提供用于调度、执行和管理同时运行的操作的基础结构来解决了这种问题。此基础结构基于使用作业来表示可以异步运行的工作单元。

作业

Job 类表示与其它作业同时运行的异步工作单元。要执行任务,插件将创建作业,然后调度它。一旦调度了作业,就会将它添加至由平台管理的作业队列。平台使用后台调度线程来管理所有暂挂的作业。当运行的作业完成时,就会从队列中除去它,平台将确定接下来运行哪一个作业。当作业变为活动状态时,平台就会对它调用 run() 方法。通过下面这个简单的示例很好地说明了作业:
   class TrivialJob extends Job {
      public TrivialJob() {
         super("Trivial Job");
      }
      public IStatus run(IProgressMonitor monitor) {
         System.out.println("This is a job");
         return Status.OK_STATUS;
      }
   }
在以下片段中创建并调度了作业:
   TrivialJob job = new TrivialJob();
   System.out.println("About to schedule a job");
   job.schedule();
   System.out.println("Finished scheduling a job");
此程序的输出与计时有关。即,对于创建了作业并且调度了作业的线程来说,没有方法可确定何时将执行作业的 run 方法。输出将为下面两种情况的其中一种:
   About to schedule a job
   This is a job
   Finished scheduling a job
或者:
   About to schedule a job
   Finished scheduling a job
   This is a job

如果想要确定在继续下一步之前作业是否已完成,则可以使用 join() 方法。此方法将阻塞调用者,直到作业已完成或者调用线程被中断为止。让我们以更确定的方式改写上面的片段:

      TrivialJob job = new TrivialJob();
   System.out.println("About to schedule a job");
   job.schedule();
      job.join();
   if (job.getResult().isOk())
      System.out.println("Job completed with success");
   else
      System.out.println("Job did not complete successfully");
假定 join() 调用未被中断,则会保证此方法返回以下结果:
   About to schedule a job
   This is a job
   Job completed with success

当然,在调度作业之后立即连接它通常不是很有用,原因是这样做不能获得并行。在这种情况下,还可能直接在调用线程中从作业的 run 方法来完成该工作。稍后让我们查看有关在何处使用连接更有意义的一些示例。

最后一个片段还使用了作业结果。结果是从作业的 run() 方法返回的 IStatus 对象。可以使用此结果来从作业的 run 方法传递回任何必需对象。还可以使用结果来指示故障(通过返回严重性为 IStatus.ERRORIStatus)或者取消(IStatus.CANCEL)。

常见作业操作

我们已经了解了如何调度作业并等待它完成,但是,还可以对作业执行许多其它有意义的操作。如果调度了一个作业,然后又决定不再需要它,则可以使用 cancel() 方法来停止该作业。如果取消作业时该作业尚未开始运行,则会立即放弃而不会运行它。另一方面,如果作业已经开始运行,则将由作业来决定它是否想要响应取消。当您尝试取消作业时,使用 join() 方法来等待取消作业早晚会有用的。以下是用于取消作业并在继续下一步之前一直等到作业完成为止的习惯用语:

      if (!job.cancel())
      job.join();

如果取消没有立即生效,则 cancel() 将返回 false,调用者将使用 join() 来等待成功取消作业。

比取消操作程度稍轻的方法是 sleep() 方法。对于此方法,如果作业尚未开始运行,则此方法将导致该作业被无限期挂起。平台将仍然记住该作业,而 wakeUp() 调用将把该作业添加至等待队列,最终还是将执行该作业。

作业状态

作业在它的生存期内将经历几种状态。不但可以通过诸如 cancel()sleep() 的 API 来处理它,而且在平台运行和完成该作业时,它的状态也会更改。作业可以经历下列状态:

如果作业当前处于等待状态,则只能将它置于休眠状态。唤醒正在休眠的作业将使它重新回到等待状态。取消作业时将使它返回到状态。

如果插件需要知道特定作业的状态,则它可以注册作业更改侦听器,当该作业在其生命周期内移动时就会通知该侦听器。这对于显示作业的进度或报告是很有用的。

作业更改侦听器

Job 方法 addJobChangeListener 可以用来注册特定作业上的侦听器。IJobChangeListener 定义用于响应作业中的状态更改的协议:

在所有这些情况下,都会为侦听器提供 IJobChangeEvent,它指定正在经历状态更改的作业以及作业完成时的状态(如果作业已执行)。

注意:作业还定义了 getState() 方法,该方法用于获取作业的当前状态(相对状态)。但是,此结果并不始终都是可靠的,原因是作业在不同线程中运行,并且可能会根据调用返回的时间再次更改状态。作业更改侦听器是用于发现作业中的状态更改的建议机制。

作业管理器

IJobManager 定义用于处理系统中的所有作业的协议。显示进度或使用作业基础结构的插件可以使用 IJobManager 来执行一些任务(例如,暂挂系统中的所有作业、找出哪个作业正在运行或者接收关于特定作业的进度反馈)。可以使用 Platform API 来获得平台的作业管理器:

      IJobManager jobMan = Platform.getJobManager();

对系统中所有作业的状态都感兴趣的插件可以对作业管理器注册作业更改侦听器,而不是对各个作业都注册侦听器。

作业系列

有时,插件将一组相关作业作为单个单元来处理会更容易。这可以通过使用作业系列来完成。作业通过覆盖 belongsTo 方法来声明它属于某个系列:

   public static final String MY_FAMILY = "myJobFamily";
   ...
   class FamilyJob extends Job {
      ...
      public boolean belongsTo(Object family) {
         return family == MY_FAMILY;
      }
   }
IJobManager 协议可以用来取消、连接、休眠或查找一个作业系列中的所有作业:
   IJobManager jobMan = Platform.getJobManager();
   jobMan.cancel(MY_FAMILY);
   jobMan.join(MY_FAMILY, null);

由于作业系列是使用任意对象表示的,因此,可以将感兴趣的状态存储在作业系列本身中,并且作业可以根据需要动态地构建系列对象。一定要使用完全是唯一的系列对象,以避免与其它插件创建的系列意外地交互。

通过系列还可以很方便地找到多组作业。IJobManager.find(Object family) 方法可以用来找到任何给定时间所有正在运行、正在等待和正在休眠的作业的实例。