« JPEG加工ユーティリティ集jpeg-progs 6bのZaurus向けパッケージ | Main | 最新版GSPlayer v2.25(GSPlayer2)のビルド方法 »

2006.09.10

REALbasicのマルチスレッドと同期機構関連機能

ファイルのダウンロードなど、時間がかかる処理を実行する際、単にメソッドを呼び出しただけでは処理終了まで操作不能になってしまう。
時間がかかる処理はスレッド内で実行すると、そのようなことにはならない。
ぜひともスレッドを使おう。

普通のメソッドをスレッド化する手順

1.プロジェクトウインドウで右クリック、追加からクラスを選択等の方法でプロジェクトにクラスを追加する。

2.「Class1」となっているデフォルトのクラス名から名前を適当に変更し、追加したクラスの「Super」を「Thread」にする。説明の便宜上「myThread」というクラス名にしたと仮定。

3.「myThread」内にイベントハンドラ「Run」が現れるので、ここにボタンの「Action」等に書かれている(処理に時間のかかる)メソッドをコピペする。メソッド内にオブジェクトへの参照が含まれる場合は、適切な形に直す必要がある。

例) EditField1.Text = "hoge" → Window1.EditField1.Text = "hoge"

4.今までメソッドを直接記述していたボタンの「Action」には、例えば以下のように書く。

Dim t As New myThread
t.run

これで、これだけでスレッドとして実行できる。

スレッドに引数を渡す

当然、スレッドに引数を渡したくなるだろう。
しかし、runイベントには引数を渡せない。
myThread内にプロパティを作り、それを引数代わりに使うのだと思われる。
例えば、「s」というString型のプロパティを作ったとする。
値を代入しておき、しかる後にrunするといった感じになる。

Dim t As New myThread
t.s = "test"
t.run

runイベントの中では、プロパティ「s」の値を使えばいい。

myThread内にメソッドを追加できる。スコープ次第で外から引数を渡しつつ実行できるが、この場合はスレッドとしての実行にならないので注意。
runイベントから呼び出す分にはOK。

複数のスレッドを実行する

この辺りから情報が怪しくなってくるので注意。
まー日本語で書かれた情報が無いよりはいいでしょう?

同じスレッドをたくさん実行したい場合、どうするか?
myThread型の配列を宣言したくなるが、これはうまくいかない。
myThread型の変数に対して繰り返しNewすればいいようだ。

dim t As myThread
dim i as integer

for i = 0 to 10
t = new myThread
t.s = "test" + str(i)
t.run
next

ただ、これだと複数スレッドの実行自体はうまくいくものの、特定のスレッドを操作できないので不都合がある。
これ以上のことはよく分からん。

スレッドのメソッドとプロパティ

最近のREALbasicはスレッド関係が強化されていて、プロパティ、メソッドともに増えている。
詳細はリファレンス参照。

プロパティ

Priority: 優先度。引数で与えられた数字が大きいほど高いCPU占有度で実行される。引数は1〜10
StackSize:
State: スレッドの状態を4段階で表現
ThreadID: UNIXにおけるプロセスIDみたいなもの。スレッドそれぞれにユニークな番号が割り振られる

メソッド

Kill: スレッドを停止
Resume: Suspend状態のスレッドを再活性化
Run: 実行
Sleep: 引数に与えられたミリ秒分スレッドを一時停止。タイムアウト後は自動的に再活性化
Suspend: スレッドをResumeが実行されるまで永久的休眠状態へ移行

REALbasicの三つの同期機構について

マルチスレッド、またはアプリケーションの多重起動が想定される環境で共有のリソースにアクセスする際には、スレッド/プロセス間の衝突を防ぐための同期機構が必要になる。
REALbasicには、Semaphore、CriticalSection、Mutexの三つが用意されている。

Semaphore

Window等のプロパティ等、スレッドから見えるところにパブリックスコープでSemaphore型の変数を用意しておき、

sm as Semaphore

Openイベントなどスレッド実行前にインスタンスを作っておく。

sm = new Semaphore

そして、myThread内の、共有リソースへのアクセスを行なうメソッドの前後を、

sm.Signal
sm.Release

で囲む。例えば、

sm.Signal
// ファイルへ書き出し処理
sm.Release

などとする。イメージとしては、Signalが入場券発行所、Releaseが入場券回収所みたいな感じだ。
その「入場券」は、有限のものとして設定される。同時に「場内」に進入できるのは「入場券」の枚数次第となる。

Semaphoreのコンストラクタに渡す引数が、その「枚数」設定。
引数は、管理するリソースの数に合わせて設定する。例えば五つまで同時アクセス可能なデータベースへのアクセスを行なうメソッドを制御するなら、

sm = new Semaphore(5)

などとしてインスタンスを生成する。これによって、実行が同時五つまでに抑制される。デフォルトは1。

あるスレッドがsm.Signalを実行した時点でSemaphoreの値が0の場合は、Semaphoreが1以上になるまで保留状態に置かれ、そのスレッドの処理はそこで止まり、Semaphoreが1以上になった時点で処理が再開される。
TDLの人気アトラクションで、入場制限がかかっている状態を連想するといい。

CriticalSection

コンストラクタに引数を渡さないSemaphoreのデフォルトとほぼ同じ動作。
Semaphoreと違って、コンストラクタに引数は渡せない。管理できるリソースの数は一つだけ。
Signalの代わりにEnter、Releaseの代わりにLeaveを使う。

Windowオブジェクト等にcriticalSection型のPropertyをパブリックスコープで作る。

cs as criticalSection

Semaphore同様Openイベント等でスレッド実行前にインスタンスを生成しておく。

cs = new CriticalSection

そして、myThread内の、共有リソースへのアクセスを行なうメソッドの前後を、

cs.Enter
cs.Leave

で囲む。ほとんどSemaphoreと同じ。

CriticalSectionはSemaphoreと違って、複数回Enterできる。複数回のEnterからリソースを開放するには、同数のLeaveが必要。
再帰処理において使うのだと思われる。

Mutex

最近のREALbasicにはMutexもある。
アプリケーション内のみならず、OSワイドなスコープを持つ。
同じマシン上で実行されているREALbasicアプリケーションから参照可能。
複数のアプリケーションから一つのリソースを使用する際の調停や、多重起動を抑止するために使える。

Appオブジェクト内にプライベートなスコープのMutex型のPropertyを作る(これがミソ)。

mx as Mutex

AppオブジェクトのOpenイベントハンドラでインスタンス生成。

mx = new Mutex("myapp")

CriticalSection同様の使い方をするほか、TryEnterメソッドの返り値を判定して多重起動を抑止するなど。
既に同じ引数で生成されたインスタンスがあれば、それらとの間で同期処理が行なわれる。

if not mx.TryEnter Then
MsgBox "Please only use one instance of My Spiffy Application at a time"
Quit
end if

CriticalSection同様、Enterを複数回実行可能。もちろん、リソースの開放には、同回数のLeaveが必要。

関連リンク

TaskMng

まつもとひろゆきさんのスレッドクラス。とても便利そうだけど、いまいち自分の脳みそがどういう実装にしていいか思いついてくれない。
オブジェクト指向への理解が足りないからだと思われる。

暗号化されていないので、ソースを読み解いた暁にはスレッドへの真の理解が開けるのではないか。

OOP University:パート 26

REALbasic university Japanのスレッドの回。

|

« JPEG加工ユーティリティ集jpeg-progs 6bのZaurus向けパッケージ | Main | 最新版GSPlayer v2.25(GSPlayer2)のビルド方法 »

Comments

Win版で試しています。
Mutexを使って多重起動の制限はかけることができたのですが、Macのアプリのように、多重起動しようとしたときに既に走っているアプリを最前面に持ってくる方法はありますか?

Posted by: はは | 2009.05.30 at 06:56 PM

やったことは無いのですが、User32.dllのSetForegroundWindowをDeclare Functionして使うようですね。

http://www.monkeybreadsoftware.de/listarchive-realbasic-getting-started/2005-07/thread-23_127.shtml

SetActiveWindowという関数もあるようです。

Posted by: moyashi | 2009.06.01 at 09:54 PM

文字化けを修正すると下のようになるかと思うのですが、
(途中のWinTypeてのがよくわからなかったので削除)
9行目のRがわからないのと、
Error TrueとErrorMessageの宣言をどうすればいいのかわかりません。
また、これをapp/openイベントの
if not mx.tryenter then
end if
の中に書けばいいんでしょうか?

すいませんがご教示いただけませんか?


Declare Function FindWindow Lib "User32.dll" Alias "FindWindowA" (lpClassName As CString, lpWindowName As CString) as Integer
Declare Function ShowWindow Lib "User32" ( hWnd As Integer, nCmdShow As Integer ) As Integer
Declare Function BringWindowToTop Lib "User32" (hWnd As Integer ) As Integer
Declare Function SetForegroundWindow Lib "User32" ( hWnd As Integer ) As Integer

// ACTIVATE THE EXISTING WINDOW
if ShowWindow( myWin, 9 ) > 0 then
if BringWindowToTop( myWin ) > 0 then
if SetForegroundWindow( myWin ) Error True
ErrorMessage "Could not set target window to foreground"
end if
else
Error True
ErrorMessage "I Could not bring target window to front"
end if
else
Error True
ErrorMessage "Could not focus target window"
end if

Posted by: はは | 2009.06.02 at 09:46 PM

残念ながらしばらくREALbasicを使っていないため、ググったこれらをご参考いただくということでいかがでしょうか。

http://www.realsoftware.com/listarchives/realbasic-nug/2003-11/msg01432.html

http://osdir.com/ml/lang.realbasic.user/2003-11/msg01373.html

http://www.realsoftware.com/listarchives/realbasic-nug/2003-11/msg01361.html

ErrorMessageは、恐らくは適当なエラーダイアログを出すユーザー定義メソッドだと思います。任意の処理でいいと思います。
Errorっていうのも、エラー処理をするユーザー定義メソッドかと思います。

SetForegroundWindowの返り値は成功かどうかで0か0以外だそうなので、それを判定すればいいと思います。

http://msdn.microsoft.com/ja-jp/library/cc411039.aspx

mutexで多重起動が確認されたときに実行するわけですので、書くところは、

if not mx.TryEnter Then
end if

でいいと思います。

Posted by: moyashi | 2009.06.02 at 11:17 PM

できました!!!!!!
クラス名がわからないときのFunctionと
me_sjisutf8(appli,false)←日本語をShiftJISに変換する関数
を使ったら、ちゃんとmyWinに0以外の値が入り、
思い通りの動きにできました!

ありがとうございました!!!!!!


if debugbuild=false then
mx=new mutex(appli)
if not mx.tryenter then

Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As Integer, ByVal lpWindowName As CString) As Integer
Declare Function ShowWindow Lib "User32" ( hWnd As Integer, nCmdShow As Integer ) As Integer
Declare Function BringWindowToTop Lib "User32" (hWnd As Integer ) As Integer
Declare Function SetForegroundWindow Lib "User32" ( hWnd As Integer ) As Integer
//Declare Function SetActiveWindow Lib "User32" ( hWnd As Integer ) As Integer

myWin=FindWindow(0,me_sjisutf8(appli,false))
// ACTIVATE THE EXISTING WINDOW
if ShowWindow(myWin,9)>0 then
if BringWindowToTop(myWin)>0 then
if SetForegroundWindow(myWin)<9 then
else
call ShowWindow(myWin,9)
call BringWindowToTop(myWin)
call SetForegroundWindow(myWin)
end if
end if
end if
//msgbox appli+"は既に起動中です。"
quit

end if
end if

Posted by: はは | 2009.06.03 at 07:36 PM

おお、よかったですね。

Posted by: moyashi | 2009.06.04 at 06:52 PM

Post a comment



(Not displayed with comment.)




TrackBack

TrackBack URL for this entry:
http://app.cocolog-nifty.com/t/trackback/13760/11724870

Listed below are links to weblogs that reference REALbasicのマルチスレッドと同期機構関連機能:

« JPEG加工ユーティリティ集jpeg-progs 6bのZaurus向けパッケージ | Main | 最新版GSPlayer v2.25(GSPlayer2)のビルド方法 »