RealBasic University

このコラムはStone Table Softwareのオーナーであり、またREALbasic Developerの編集者でもあるMarc Zeedar氏により書かれたものを、著者の許可を得て翻訳したものです。この翻訳はHREM Researchにより提供されています。この日本語版へのご意見はRBU-Jまでご連絡下さい。

URL: http://www.applelinks.com/rbu/105/
INDEXに戻る

OOP University:パート 26

前回、再利用可能なプログレス・バーのダイアログを、どのように作成するかについてデモンストレーションを行いました。それは、どのようなプログラムにもプログレス・バーを追加することがとても簡単にできました。ところが、使用されたメソッドがうまく機能しない状況がありました。今回は、いくつかの代替案を検討していきましょう。

タイマーとスレッド

我々の再利用可能な進行状況表示ダイアログでは、ユーザがキャンセルしたときに警告してくれるグローバルプロパティgPleaseStopProgressを使用するようにしました。これはダイアログそれ自体は実行されているタスクについては何も知ることはできませんし、タスクはキャンセルされたかを知るために何らかの方法が必要だったからです。

ところがこのメソッドは、いくつかの欠点があり、すべての状況でうまく機能しない可能性があります。一つは、それはダイアログを用いるすべてのプロジェクトに、グローバルプロパティを追加しなければなりません。もう一つは、それがキャンセルされたかどうかを確認するために、タスクは定期的にグローバル・プロパティのステータスをチェックしなければなりません。簡単なループのタスクでは何の問題もありませんが、繰り返しのない複雑なタスクでは、タスクを中断してキャンセルされたかを何回もチェックする必要があるでしょう。

またREALbasicでの通常処理にすぐに応答しないようにみえるイベントもあります。たとえば、シリアル通信中にプログレス・バーが更新されないという問題があるというお手紙をMatt Eastburnさんから頂きました。

Marcさん、こんにちは。

また私(RealBASICのwindowsユーザ)です。私の今回の質問は、プログレス・バー(そしてシリアル通信)を使用することに関連しています。

私の問題は、プログレス・バーがシリアル通信コントロールで使用されているときには、更新してくれないということです。私は次のようなものを組みました。

  
sub DataAvailable()
// シリアル・バッファーにあるすべての内容を読む
Buffer = Buffer + SerialDriver.readAll

//Serial_In_TF.text = Buffer

//ProgressBar1を更新―酸素計に10時間あると設定します
ProgressBar1.Value = len(Buffer)
ProgressBar1.refresh

// 送信される最後の文字なので
// xを受信したらプログレス・バーを前に進める
if Right(Buffer, 1) = "x" then
ProgressBar1.Value = 20000
ConvertBuffer
end if

if len (Buffer) > 0 then
end if

end sub

よってオープン・イベントで、私は--> ProgressBar1.Maximum = 20000に設定しました。

私がそれを更新する唯一の方法は、Window 1上でマウスを動き回すことです。さらに、私がマウスを動き回さない限りは、SerialDriverのすべての内容をダウンロードしないようです。私の問題がどういうものであるか何か見当はつくでしょうか。

Matt Eastburn

私はMattさんのコードをいくらか検討してみましたが、一応ある程度は正しいようです。私はマウスを動かす必要があるということはありませんでしたが、シリアル通信中は再利用可能なプログレス・バーは更新されませんでした。それは、シリアルポートの監視が、通常のタスク中は普通になされるキャンセルの監視を何らかの理由でブロックするようです。

私は、ループ中にコマンド―ピリオドを自力でテストするコードを含めるという回避策を作成するに至りました。一番すぐれた方法ではありませんが、うまく作用しました。(もちろん、MattさんのWindowsマシンでは、彼はコマンド―ピリオドではなくEscキーをチェックするでしょう。)

興味があるのであれば、私のマシンでどのようにMattさんのコードを機能するように変更したかをここに示します。

  
dim s as string
dim amount as integer

if cancelCheck.value and keyboard.asyncCommandKey and keyboard.asyncKeyDown(&h2F) then
progressDialog.updateProgress("User aborted...", 20000)
progressDialog.stopProgress
serialDriver.close
else

// シリアル・バッファーにあるすべての内容を読む
s = SerialDriver.readAll
amount = len(s)
Buffer = Buffer + s

// ProgressBar1を更新―酸素計に10時間あるとした
s = "Item " + str(len(Buffer)) + " out of 20,000"
progressDialog.updateProgress(s, amount)

// 送信される最後の文字なので
// xを受信したらプログレス・バーを前に進める
if Right(Buffer, 1) = "x" then
progressDialog.stopProgress
end if
end if

これはserialDriverserialコントロール)のdataAvailableイベント内部におきます。

異常な状況でもうまく機能する別のテクニックもあります。

ひとつの方法は、timerコントロールを使うことです。それを一定間隔(だいたい1秒ごと)に機能するように設定し、それが実行された時はuserCancelledtrueであるかをチェックさせます。

残念なことに、タイマーは優先順位が低く、コンピュータが非常に忙しいときは実行される可能性が少ないので、あなたのタスクがCPUに大きな負荷をかけるもの(たとえば重い計算)であるとこれはうまく機能しないでしょう。

もっとよい方法は、タスクをスレッドに分割することです。スレッドは人々を怖がらせる傾向にあります。しかし、ある状況においては複雑になり、コツもありますが、スレッドを扱うのは本当は簡単です。RBUではこれまでスレッドを取り扱ってきませんでしたので、これはよい導入の機会です。

まずはじめに、「スレッド」とは何かを理解してください。それは自分自身で実行する独立したタスクのことです。スレッドではMac Finderのような一つのプログラムが、同時に複数のことを実行することができます(たとえば、他のコマンドに応答している間に、ファイルの一セットをコピーすること)。

REALbasicでは、あなたが長いタスクをはじめたとき、通常はそのタスクが終了するまですべてのプログラムがストップします。あなたがそのタスクをスレッドにすれば、プログラムはストップしません。ユーザは依然プログラムと交信することができます。もちろん、タスクが終了するまでは、そのタスクで生成されたデータや結果はどれも利用できませんが、少なくともユーザはプログラムで作業を継続することができます。

さて、ここがスレッドが複雑になるところです。あなたがプログラムを書いているときは、一般的にあなたは線形的に(すなわち段階的に)物事が起こることを期待します。ユーザが「ソート」ボタンをクリックすると、あなたはソート・ルーチンを呼び出します。またユーザがプリントをクリックすると、あなたはプリント・ルーチンを呼び出します。しかし、スレッドはそのすべてを変えます。あなたのソート・ルーチンがスレッドであると、ユーザはソートを開始して、それからソートが完了する前にデータを印刷することもありえるでしょう(おそらく、それを望んではいないでしょうが)。

また、コード内の相互依存性もあります。あるルーチンはあるタスクをthreadで処理しています。1番目が終了していないと、2番目のルーチンは次のタスクの部分を処理したくてもできません。この種の状況では、プログラムのすべての処理は、実行する前に前の処理が完了したことを確認する必要があります(たとえば、各々の処理で「終了」プロパティを設定します)。

別の問題は、2つの異なるルーチンが同じデータを処理する必要があるときです。ある一つのスレッドでソート中のデータベースがあるとき、別のスレッドがその同じデータのレコードを削除したり追加したりしようとしたらどうなるでしょうか?この問題の解決策は、それを予見してデータを「ロック」するシステム(フラグを設定して)を作成して、ロックが解除されるまでは別のルーチンがデータに手を出さないようにします。REALbasicではsemaphoreクラスでこれを簡単に行うことができます。

スレッドに関するこれらの問題は複雑ですが、上級ユーザにそれを委ねることにして、今回のチュートリアルではこれ以上立ち入りません。私はスレッドが提起するもっと複雑な問題のいくつかについてあなたに理解して欲しいと思います。しかしながら、ほとんどの場合では、スレッドの用法は直接的で分かりやすいものです。

今回の我々の目的では、バックグラウンドで処理する必要のある簡単かつ時間のかかるタスクがあると仮定しましょう。そして、もしユーザが希望すればキャンセルできるようにしましょう。例として、我々が以前に用いた一つのプログレス・バーのコードを使用しましょう。今回の場合は、それをスレッド内から実行する点が違います。

まずはじめに、threadオブジェクトを作成する必要があります。プロジェクトに新しいクラスを追加(ファイルメニュー、「新規クラス」)することでこれを行います。そのクラスをlongProcessThreadClassと命名してください。ダブルクリックしてそれを開き、そのrunイベントに行ってください。そこで次のコードを入力します。

  
dim i as integer
dim s as string

const bigNum = 1000000

// progressDialogの初期化
progressDialog.init("Single bar count", true, window1.cancelCheck.value)
progressDialog.startProgress("Counting...", bigNum \ 1000)

for i = 1 to bigNum
// 1000で均等に割られた場合
if i / 1000 = i \ 1000 then
s = "Item " + str(i) + " out of " + str(bigNum)
progressDialog.updateProgress(s, 1)
end if
if window1.cancelCheck.value and gPleaseStopProgress then
progressDialog.updateProgress("User aborted...", bigNum \ 1000)
exit
end if
next // i

// すべて完了したので、ダイアログから抜ける
progressDialog.stopProgress

このコードは、我々が前回使用したものとほとんど同じです。唯一の違いは、cancelCheckコントロールがwindow1上にあることを明記したことです。

さて、それのインスタンスを作成するために、longProcessThreadClasswindow1上にドラッグして、わかりやすくするためにインスタンスの名前をlongProcessThreadInstanceと変更してください。

window1上に新しいpushButtonを作成し、そのキャプションを「スレッドでシングルバーをテスト」として、actionイベント内に次を入力してください。

  
longProcessThreadInstance.run

これで完了です!これであなたはテスト・プログラムを実行することができ、新しいボタンをクリックするとスレッドが実行されるでしょう。プログラムは、多くの動作を妨げるモーダル・ダイアログ(プログレス・バーのダイアログ)を表示していること以外は、あなたの動作によく反応します。そして、プログラムはキャンセル・コマンドに反応するでしょう(遅いでしょうが)。

スレッドのコード内部で、あなたはユーザがキャンセルしたかどうかを定期的にチェックする必要がまだあることに注意してください。しかしスレッドのシステムは、プログラムのあらゆる状況を完全に停止することなく実行できるという利点があります。あなたがダイアログのないプログレス・バーを必要とするときには、スレッドの使用は理想的でしょう(すなわち、Internet ExplorerやSafari、あるいは他のウェブ・ブラウザーのように、ページ読み込みの進行状況を示すためにウィンドウの下部に表示されるプログレス・バーのようなもの)。

私は、このメソッドは「すぐには」キャンセルされないと上で述べました。すなわち、あなたがコマンド―ピリオドを押してもすぐには反応しませんが、あなたがキーを押し続けていると1秒か2秒後には停止するでしょう。私は、これはuserCancelledの機能の仕方の欠点のためだと思います(キーボードを頻繁に調査しなかったり、他の何かに違いありません)。asyncCommandKeyを使って、自分でチェックするコードに追加したときは、システムはすぐに反応しました。しかし、言うまでもなく私がキー・コードに関して前に述べたことの制限にぶつかりました。

わたしがどのようにrunコードを修正したかをここに示します。

  
if window1.cancelCheck.value then
if gPleaseStopProgress then
progressDialog.updateProgress("User aborted...", bigNum \ 1000)
exit
// すぐにキャンセルするためには、これらの行をコメントアウトしてください。
elseif keyboard.asyncCommandKey and keyboard.asyncKeyDown(&h2F) then
progressDialog.updateProgress("User aborted...", bigNum \ 1000)
exit
end if
end if// window1.cancelCheck.value

これは今回のプロジェクト・ファイルに含まれていますが、コメントアウトされていますので、あなたはどちらの方法でもプロジェクトをテストすることができます。あなたがWindowsや英語キーボード以外を使っているならば、チェックを修正する必要があることを忘れないでください。(キー・コードに混乱している人のために、私は次週のLettersセクションでこれらを解説します。)

今週のチュートリアル(リソースを含む)を完全なREALbasicプロジェクトを手に入れたい場合は、ここからダウンロードしてください。

次週

デバッグについてのレッスンです。

Letters

コラムで一つのことに集中したので、今回はお手紙はありません。


RBU-Jの通知サービス!コラムが発表されるたびに日本語版REALbasic Universityのお知らせの emailがあなたに届きます。登録・削除は ここ から。

INDEXに戻る