プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

Eclipseプラグイン開発: 非同期実行

Eclipse プラグイン開発 目次


 Eclipseでの非同期実行に関してはorg.eclipse.core.runtime.jobs.Jobというクラスを使います。

 

Jobの作成とタイプ


 Jobの作成は、Jobを継承して実行処理を書くか、Job.createSystemかJob.createメソッドで作成します。個人的には後者がお勧めです。

 Jobは以下の3種類の実行方法があります。GUIにどのように表示したいかによって、どれを使うかを決めて下さい。

種類 作り方 説明
システムJob createSystemで作るか、setSystem(true) UIに表示されないバックグラウンドJob
ユーザーJob setUser(tru) ダイアログが表示されるJob
通常Job UserにもSystemにもしない Progressビューには表示されるが、ダイアログは出ないJob


 10秒待機するだけのJobを実行するだけのサンプルです。

Job systemJob = Job.createSystem(makeTask("System Job"));

Job defaultJob = Job.create("Default Job!",makeTask("Default Job"));

Job userJob = Job.create("User Job!",makeTask("User Job"));
userJob.setUser(true);

systemJob.schedule();
defaultJob.schedule();
userJob.schedule();

//-----------------------------------------------

//10秒待つだけの処理を作る
private ICoreRunnable makeTask(String jobName){
  return monitor->{
    try {
      monitor.beginTask(jobName, 10);
      int i=0;
      while(i!=10){
        Thread.sleep(1000);
        monitor.worked(i);
        i++;
        if(monitor.isCanceled()){
          break;
        }
      }
    } catch (InterruptedException e) {
    }
    monitor.done();
  };
}

 実行するとUser Job!のダイアログが出ます。また、ProgressにDefault Job!とUser Job!が表示されています。
f:id:nodamushi:20170406020429p:plain

Jobの実行ルール

 あるJobが実行している間は他のJobをスタートしたくない場合があります。Jobはそういったルールを指定することが可能です。
 IResourceはこのルールを実装していて、親子関係にあるIResourceをルールに持つJobが実行している間は、実行できなくすることが可能です。

IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();

IProject aProj = root.getProject("A");//  /A/
IFile file = aProj.getFile("File.txt");// /A/File.txt
IProject bProj = root.getProject("B");//  /B/

Job aJob = Job.create("A Project Job", makeTask("A Project Job"));
Job fileJob = Job.create("File.txt Job", makeTask("File.txt Job"));
Job bJob = Job.create("B Project Job", makeTask("B Project Job"));

aJob.setRule(aProj);
fileJob.setRule(file);
bJob.setRule(bProj);


aJob.schedule();
fileJob.schedule();
bJob.schedule();

f:id:nodamushi:20170406020430p:plain
 

 A Project JobとB Project Jobは親子関係似ないので、同時に実行されています。一方、File.txt JobはA Project Jobと親子関係なので、A Project Jobが完了するまで待機状態になります。



 なお、実行順序を逆にすると、今度はA Project JobがFile.txt Jobが完了するまで待機します。

fileJob.schedule();
aJob.schedule();

f:id:nodamushi:20170406020431p:plain


Ruleを自作する

 ルールはISchedulingRuleを実装することで、自分で定義することも可能です。

メソッド 説明
contains 引数が子であるかどうか。自分自身も子であると見なす
isConflicting 衝突関係にあるかどうか。衝突していない場合は実行可能。

 

 ファイルパスなどの単一親の木構造ルールではなく、複数の親を持つルールを定義してみましょう。

public class MyRule implements ISchedulingRule{
  private MyRule[] parents;

  public MyRule(MyRule... parents){
    this.parents = parents!=null?parents:new MyRule[0];
  }

  @Override public boolean contains(ISchedulingRule rule){
    //※自分は必ずtrueにしなくてはならない。
    if(rule == this)return true;

    if(rule instanceof MyRule){
      for(MyRule r:((MyRule)rule).parents){
        if(contains(r))return true;
      }
    }
    return false;
  }

  @Override public boolean isConflicting(ISchedulingRule rule){
    if(rule instanceof MyRule){
      MyRule other = (MyRule)rule;
      //※衝突ルールは双方向
      //  (子が実行していても親が実行していても衝突)
      return contains(other) || other.contains(this);
    }
    return false;
  }
}

 


 以下のようなテスト実行をしてみました。Job Bを遅延実行しています

MyRule a = new MyRule();
MyRule b = new MyRule();
MyRule ab = new MyRule(a,b);

Job aJob = Job.create("Job A", makeTask("A"));
Job bJob = Job.create("Job B", makeTask("B"));
Job abJob = Job.create("Job [A/B]", makeTask("A/B"));

aJob.setRule(a);
bJob.setRule(b);
abJob.setRule(ab);

aJob.schedule();
bJob.schedule(1000);
abJob.schedule(2000);

f:id:nodamushi:20170406020432p:plain
 

 A,Bの両方が完了するまで、Job [A/B]が待機していることが確認できます。


Workspaceでアトミックな処理をするJobを作る

 2005-04-12 - NetPenguinの日記によると、リソースを処理する作業は全てのプラグインにまたがって単一にしないと、失敗する場合があるそうです。(知らなかったー)そういった場合は、WorkspaceJobクラスを利用することでその目的を達成できます。

 なお、Jobのように非同期に実行したくない場合は、ResourcePlugin.getWorkspace().runを使うことも出来ます。

Jobをグループ化する

 複数のJobを走らせるとき、全てのJobが完了するまで待機したい、全てのJobを一気にcancelしたい場合などがあります。JobGroupでグループを設定することで、これらの目的を実装できます。

int maxThread = 2;//動作させる最大スレッド数
int seed = 1;//基本的に1にしておけば良い。
JobGroup group = new JobGroup("ab group", maxThread, seed);
aJob.setJobGroup(group);
bJob.setJobGroup(group);
abJob.setJobGroup(group);

aJob.schedule(); bJob.schedule(1000); abJob.schedule(2000);


try {
  //全部のJobが終わるまで待機
  //0にするとタイムアウトしない
  group.join(0, null);
} catch (OperationCanceledException|InterruptedException e) {
  e.printStackTrace();
}

 

 seedはGroupが管理する「初期」サイズで、初期値より多いJobをグループ化しても問題ありません。むしろ、初期値が大きすぎる方が問題で、初期サイズのJobが全て完了しない限りjoinで待機し続けることになります。
 後から他のスレッドでJobを遅延定義すると言った場合でも無い限りは、seedは1にしておけば良いでしょう。

int seed = 4;// Jobの数(3個)より大きい

//-------------------------------

group.join(0, null);//永遠にjoinしない