並行性インフラストラクチャー

複雑なシステムでは、主に、タスクの実行中に応答性を保つことが困難になります。この問題は、同時に実行されるよう設計されていない複数のコンポーネントが同じリソースを共有する拡張可能システムでは、さらに大きくなります。 org.eclipse.core.runtime.jobs パッケージでは、同時に実行する操作のスケジュール、実行、および管理を行うためのインフラストラクチャーを用意することによって、この問題に対応します。このインフラストラクチャーは、ジョブ の使用に基づいて、非同期実行できる作業単位を表します。

ジョブ

ジョブ・クラスは、その他のジョブと並行して実行されている非同期の作業単位を表します。タスクを実行する場合、プラグインはジョブを作成して、そのジョブをスケジュール します。ジョブはスケジュールされると、プラットフォームで管理されるジョブ・キューに追加されます。プラットフォームは、バックグラウンド・スケジューリング・スレッドを使用して、保留中のすべてのジョブを管理します。実行中のジョブが完了すると、そのジョブはキューから除去され、プラットフォームは次に実行するジョブを決定します。ジョブがアクティブになると、プラットフォームは、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() メソッドから戻された IStatus オブジェクトです。この結果を使用すると、ジョブの実行メソッドから戻された必要なオブジェクトを渡すことができます。また、この結果を使用して、失敗 (重大度 IStatus.ERRORIStatus を戻すことによって) やキャンセル (IStatus.CANCEL) を示すこともできます。

共通のジョブ操作

ジョブのスケジュールおよびジョブの完了の待機方法について説明しましたが、この他にも興味深いジョブ操作があります。ジョブをスケジュールした後にこのジョブが必要なくなった場合は、cancel() メソッドを使用してこのジョブを停止できます。ジョブがキャンセルされたときにまだ実行されていなかった場合、このジョブは即時に廃棄され、実行されません。一方、既に実行されていた場合は、ジョブがキャンセルに応答するかどうかはジョブ次第です。ジョブをキャンセルする場合は、join() メソッドが使用できるようになるまで待機します。以下に、ジョブをキャンセルし、ジョブが完了するまで待機してから続行するための共通イディオムを示します。

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

キャンセルが即時に反映されない場合、cancel() は false を戻し、呼び出し元は join() を使用して、ジョブが正常にキャンセルされるまで待機します。

sleep() メソッドは、キャンセルよりも緩やかなメソッドです。ジョブがまだ実行されていなかった場合、このメソッドは、ジョブを無期限に保留します。プラットフォームでは引き続きこのジョブを認識しており、wakeUp() 呼び出しを使用することによって、実行場所である待機キューにそのジョブが追加されます。

ジョブの状態

ジョブは、存続時間中にいくつかの状態になります。ジョブは cancel()sleep() などの API によって操作可能というだけではなく、プラットフォームがジョブを実行したり、完了するに応じて、その状態が変化します。ジョブは以下の状態を遷移します。

ジョブをスリープ状態に置くことができるのは、ジョブが現在待ちにある場合のみです。スリープ状態のジョブをウェイクアップすると、待ち状態に戻ります。ジョブをキャンセルすると、なし状態に戻ります。

特定のジョブの状態を知る必要がある場合、プラグインは、ジョブがライフ・サイクル中に遷移すると通知するジョブ変更リスナー を登録することができます。ジョブ変更リスナーは、進行状況を示したり、その他の場合にジョブに関して報告するときに役立ちます。

ジョブ変更リスナー

ジョブ・メソッド addJobChangeListener を使用すると、特定のジョブでリスナーを登録できます。 IJobChangeListener は、ジョブの状態変更に応答するプロトコルを定義します。

いずれの場合も、リスナーには、状態が遷移するジョブおよびジョブの完了時の状態 (完了した場合) を指定する IJobChangeEvent があります。

注: ジョブは、ジョブの (比較的) 現在の状態を取得する getState() メソッドも定義します。ただし、ジョブが異なるスレッドで実行され、呼び出しが戻るまでに再度状態が変わることがあるため、この結果は、常に信頼できるものではありません。ジョブ変更リスナーは、ジョブの状態変更を発見する推奨メカニズムです。

ジョブ・マネージャー

IJobManager は、システム内のすべてのジョブを操作するためのプロトコルを定義します。進行状況を示すプラグイン、あるいはジョブ・インフラストラクチャーを操作するプラグインは、 IJobManager を使用して、システム内のすべてのジョブの中断、実行中のジョブの検索、特定のジョブの進行状況フィードバックの受信などのタスクを実行します。プラットフォームのジョブ・マネージャーは、プラットフォーム API を使用して、取得されます。

      IJobManager jobMan = Platform.getJobManager();

システム内のすべてのジョブの状態に関心があるプラグインは、数多くのジョブに個別にリスナーを登録せずに、ジョブ・マネージャーにジョブ変更リスナーを登録できます。

ジョブ・ファミリー

プラグインでは、関連ジョブのグループを 1 つの単位として操作する方が簡単な場合があります。この場合は、ジョブ・ファミリー を使用します。ジョブは、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) を使用すると、一定の時間に実行中、待機中、およびスリープになっているすべてのジョブのインスタンスを見付けることができます。