(→具体例) |
(→具体例) |
||
117行目: | 117行目: | ||
==== 具体例 ==== | ==== 具体例 ==== | ||
+ | この方法は、非同期タスクを 3 秒のディレイ(遅延)経過後に 10 秒間隔で連続実行します。 | ||
This submits a task to an async thread to be executed after 3 seconds with a period of 10 seconds. | This submits a task to an async thread to be executed after 3 seconds with a period of 10 seconds. | ||
<blockquote><source lang="java"> | <blockquote><source lang="java"> | ||
122行目: | 123行目: | ||
public void run() { | public void run() { | ||
− | System.out.println(" | + | System.out.println("このメッセージは非同期スレッドから出力されています。"); |
} | } | ||
}, 60L, 200L); | }, 60L, 200L); | ||
</source></blockquote> | </source></blockquote> | ||
− | + | どちらの時間も Server Tick 単位の値のため、初回ディレイは 60/20 = 3秒、実行間隔は 200/20 = 10 秒となります。 | |
− | + | 上記のコードは、メッセージ "このメッセージは非同期スレッドから出力されています。" を10秒毎に出力します。 | |
− | |||
=== 戻り値を返すタスク === | === 戻り値を返すタスク === |
2013年4月7日 (日) 22:24時点における版
目次
スケジューラの解説
この解説では、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のインタフェースの仕様については[こちら]を参照して下さい。
遅延タスク
一定の遅延を置いてから実行されるタスクです。遅延時間にはゼロを指定可能です。
具体例
この方法では、3秒のディレイ(遅延)経過後にプラグインのメインスレッド上でタスクを実行します。
myPlugin.getServer().getScheduler().scheduleSyncDelayedTask(myPlugin, new Runnable() { public void run() { getServer().broadcastMessage("This message is broadcast by the main thread"); } }, 60L);
ディレイには、 Server Tick 単位で 60 を指定しています。Server Tick は 1/20 秒に相当するので、今回の例では 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.
繰り返しタスク
繰り返し実行されるタスクです。遅延時間も設定可能です。
具体例
この方法は、非同期タスクを 3 秒のディレイ(遅延)経過後に 10 秒間隔で連続実行します。 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("このメッセージは非同期スレッドから出力されています。"); } }, 60L, 200L);
どちらの時間も Server Tick 単位の値のため、初回ディレイは 60/20 = 3秒、実行間隔は 200/20 = 10 秒となります。 上記のコードは、メッセージ "このメッセージは非同期スレッドから出力されています。" を10秒毎に出力します。
戻り値を返すタスク
スケジューラから戻り値を返す事ができるタスクです。メインスレッドから実行可能なタスクです。
この仕組みの目的は、他タスクから呼ばれた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....