普段スクリプトを組んでいるときにはほとんど意識されませんが、一つのアプレットに対して複数の命令がほぼ同時に受け渡されることは十分考えられます。CGIなどでは特に顕著ですが、他にもネットワーク経由で複数のユーザがアクセスするようなアプレットや、複数のアプレットからアクセスされるライブラリアプレットなどを作成した場合は、かならずこういった事態が生じることになります。これが問題になるのは、複数の処理が同時に行われた場合、ある処理で用いるグローバル変数や属性の内容が他の処理によって変更されてしまう可能性があるためです。当然ながら、正しくない値を用いた処理が正しい結果を返すはずはありません。
ここでは、その問題の生じる機序と解決策を論じます。
機序 現在のMacOSは、プリエンプティブなマルチタスクを実現していません。従ってAppleScriptに限らず、MacOS上で動作するすべてのアプリケーションは完全に独立して動作することができないのです。
殊にAppleScriptはスレッドマネージャにすら対応していないため、動作を並行して行うことは不可能です。こういった場合、並行して処理されるはずの動作は時間的に連続して処理されることになります。
例えば、あるアプレット「Lib」に「Sample(x)」というハンドラがあるとします。このハンドラの実行には10秒の時間がかかるとし、その処理の最初には処理1が、最後には処理2があるとしましょう。
on Sample(x)
(処理1)
c
(10秒)
c
(処理2)
end Sampleこのアプレットに対して、アプレット「A」から「Sample(a)」という命令が送られてくると、アプレット「Lib」は処理を開始します。この時、Aによる処理1、2をA1、A2とします。
さて、アプレット「Lib」がAからの命令を実行している途中、アプレット「B」から「Sample(b)」という命令が送られてきた場合、このアプレットはどうするでしょうか。普通に考えれば、「A1→A2→B1→B2」と処理しそうですよね。しかし実際には「A1→B1→B2→A2」になってしまいます。
これはAppleScriptが「新たな命令が送られてきた場合、現在の処理を一時中断して新たな命令を先に処理する」という仕組みになっているからです。これは田中求之氏によると「 re-entrance behavior」と呼ばれるそうですが、この事を念頭に置いて上の動作を見ると、なるほどと納得できますね。なぜそういう仕様なのかは分かりませんが、とりあえず実際の動作が分かれば対策を考えることはできます。
もっとも re-entrance behaviorが常に成立しているというわけではないようです。上の例で言えば「A1→B1→A2→B2」となるような場合も何度か目にしましたので…
解決策 冒頭でも述べたように、このような状況でもっとも問題になるのは、ある処理中で使用されていたグローバル変数や属性が他の処理によって変更されてしまう可能性があるということです。先の例で言えば、A1の部分でデリミタ(グローバル変数)を":"に変更し、A2の部分でリストを文字列に変換する場合を考えてみると分かるでしょう。もしB1〜B2の部分でデリミタを違うものに変更してしまうと、A2での変換では本来返されるはずの文字列を返しません。
こういった問題を避けるためには、次のような方法があります。
まず、グローバル変数を使用しないことです。すべてローカル変数とパラメータの受け渡しで処理すれば、グローバル変数の書き換えによる問題は根本的に生じません。これは属性も同じことです。
しかし、デリミタや属性をまったく使用しないというわけにもいかないので、使用する場合の注意を。
グローバル変数や属性を使用する場合は、それを変更する際に前もってその時点での値をローカル変数に取り込んでおき、使い終わった後すぐにもとの変数または属性に書き戻します。
例えば次のように書いて下さい。
set CurDelim to AppleScript's text item delimiters
set AppleScript's text item delimiters to ":"
(デリミタを使った処理)
set AppleScript's text item delimiters to CurDelimこうすることで、他の処理が割り込むことによるグローバル変数の問題を抑え込むことができます。
最後に こういった問題は、突き詰めていくとどんどんスクリプティングを困難にしてしまいます。CGIやライブラリなど常に複数の命令を処理する可能性のあるアプレットについては動作の確実さを優先するべきですが、普段使用するアプレットについてそこまで厳密にグローバル変数を排除する必要はないと思います。
しかし、将来的に自分のスクリプトをハンドラに切り分ける場合やCGIを扱う場合などを考慮すれば、最初からグローバル変数を使わないようにした方が無難だとは思います。