目次
スケジューラの解説
この解説では、Bukkitのスケジューラの利用方法を説明しています。
主な注意事項
主な注意事項は次の2点です。
- 他のスレッドからAPIのメソッドを(一部例外あり)を呼び出さない
- メインのスレッドをスリープ状態にしてはならない
解説
スケジューラは次の3種類のタスクをオプションとして提供しています。
- 遅延タスク(Delayed Task)
- 連続タスク(Repeating Task)
- 戻り値を返すタスク
これらのタスクは2種類のスレッドとして実行する事ができます。
- メインサーバスレッド
- 専用スレッド
スレッドの種類
スケジューラにどのスレッドタイプを利用すれば良いかが、最も重要な問題です。
間違ったスレッドタイプを利用すると、検出が困難な障害を断続的に発生させる原因となります。
また、そのような障害は試験サーバでは発生せず、頻繁に連続稼動しているサーバでのみ発生する場合がある等、
再現性が低い障害となる場合もあるため、原因の特定を更に困難にします。
Minecraftサーバはマルチスレッドであり、
一つのメインサーバーのスレッドと、他のヘルパースレッドが存在します。
多くのデータ構造は、サーバのメインスレッドからのみアクセスされます。
別のスレッドからこれらのデータを変更しようとすると、
2つのスレッドが同時にデータにアクセスする事になるため、
データ破損を引き起こす可能性があります。
メインスレッドは、サーバーのCPU時間の配分を処理します。
このスレッドは、他のタスクへCPU時間を配分するために、秒間20回のスリープを実行します。
少数のBukkitAPIはスレッドセーフです。
あなたのプラグインが使っているAPIがスレッドセーフなものである事を確認していない限り、
基本的にBukkitAPIの命令はスレッドセーフではないものとして開発して下さい
同期タスク
サーバのメインスレッドが実行するタスクです。
- 重要: このスレッドが停止すると、サーバがフリーズします。
メインスレッドは下記の用途に使えます
- スケジューラメソッドの呼び出し
- スケジューラの戻り値を取得する際は注意を要します(後方の説明に記載)
- 停止・待機処理を行わないタスク
メインスレッドは下記の用途に使わなくてはいけません
- Bukkit APIのメソッドを呼び出すタスク(スレッドセーフな方法を除く)
メインスレッドは下記の用途に使ってはいけません
- 自スレッドをスリープ状態にするタスク
- 停止・待機処理を含むスレッド。(例:ネットワーク通信の受信タスク)
メインスレッド上で同期タスクを実行する事自体は良いのですが、
待機や停止の時間がほぼゼロであるものに限ります。
- 訳者注: 1つの処理ステップが長時間を費やす処理は、サーバ自体をその間待機状態に置いてしまうため、メインスレッド上で行うべきではない事を説明しています。
非同期タスク
非同期タスクは、サーバのメインスレッド以外のスレッドによって実行されます。専用のタスクとして実行されるため、同期タスクで説明したような問題を引き起こすことなく、自タスクを停止・スリープ状態にする事ができます。この特性は、同期タスクの真逆に相当します。
非同期タスクは下記の用途に利用できます
- スケジューラメソッドの呼び出し
- プラグインAPIを利用しないタスク
非同期タスクは下記の用途に利用しなければなりません
- 自スレッドをスリープ状態にするタスク
- 停止・待機処理を含むスレッド。(例:ネットワーク通信の受信タスク)
非同期タスクは下記の用途に利用してはいけません
- Bukkit APIのメソッドを呼び出すタスク(スレッドセーフな方法を除いては、下記参照)
タスクの種類
タスクには、連続タスク, 遅延タスク, 戻り値を返すタスク, の3種類があります。
APIのインタフェースの仕様については[こちら]を参照して下さい。
遅延タスク
一定の遅延を置いてから実行されるタスクです。遅延時間にはゼロを指定可能です。
具体例
This submits a task to the main thread to be executed after 3 seconds.
myPlugin.getServer().getScheduler().scheduleSyncDelayedTask(myPlugin, new Runnable() { public void run() { getServer().broadcastMessage("This message is broadcast by the main thread"); } }, 60L);
The time delay is 60. This counts in server ticks. There are 20 ticks per second, so the delay is 60/20 = 3.
This code could be executed by another thread. If the code
getServer().broadcastMessage("This message is broadcast by an async thread");
was executed by another thread, it could cause problems since the broadcastMessage method is being executed by another thread.
繰り返しタスク
繰り返し実行されるタスクです。遅延時間も設定可能です。
具体例
This submits a task to an async thread to be executed after 3 seconds with a period of 10 seconds.
myPlugin.getServer().getScheduler().scheduleAsyncRepeatingTask(myPlugin, new Runnable() { public void run() { System.out.println("This message is printed by an async thread"); } }, 60L, 200L);
Both times count in server ticks. The initial delay is 60/20 = 3 seconds and the period is 200/20 = 10 seconds.
The above code will print "This message is printed by an async thread" every 10 seconds.
戻り値を返すタスク
スケジューラから戻り値を返す事ができるタスクです。メインスレッドから実行可能なタスクです。
この仕組みの目的は、他タスクから呼ばれたAPIのメソッドに戻り値を返させる事です。
スケジューラは、戻り値として返されるであろうオブジェクトを取得可能にします。
具体例
Future<String> returnFuture = myPlugin.getServer().getScheduler().callSyncMethod(myPlugin, new Callable<String>() { public String call() { return "This is the string to return"; } }); try { // This will block the current thread String returnValue = returnFuture.get(); System.out.println(returnValue); } catch (InterruptedException e) { System.out.println("Interrupt triggered which waiting on callable to return"); } catch (ExecutionException e) { System.out.println("Callable task threw an exception"); ee.getCause().printStackTrace(); }
This will submit the callable and then use the Future.get() method. This method sleeps the current thread until the Callable has returned a value and gets the result.
メインスレッドからの利用
この方法は非推奨です。 この仕組みは、メインスレッドから利用される事を想定したものではありません。
.get()
メソッドを利用している場合、メインスレッドがこのタスクを完了するまでの間、現行スレッド(=メインスレッド)をスリープ状態にします。
どうしてもメインスレッド上で使用する必要がある場合は、返されるであろうスケジューラの戻り値に対する.get()
メソッドを呼び出す前に、.isDone()
メソッドをチェックしなければなりません。さもなくばメインスレッドは、メインスレッド自身がタスクを完了させるまでスリープ状態になります。
Thread Safe API Methods
Bukkit API methods which are thread safe are:
- All the scheduler methods
- Bukkit.getServer()
- Server.getBukkitVersion()
- World.getUID()
- World.getMaxHeight()
- World.getSeed()
- World.getBlockTypeIdAt(int x, int y, int z)
- Entity.getEntityId()
- Entity.getUniqueId()
- This list is not complete.