連載:VB 6ユーザーのための
|
|
|
ではお待ちかねの、新しいスレッドを作成してそれを実行するコードに進もう。今度は、コードの前半を見てほしい。重要な部分だけ抜き出しておこう。
| |||||||||
新しいスレッドを作成し、実行するコード | |||||||||
|
スレッドを作成・実行するにはThreadクラスを利用する。ThreadクラスはSystem.Threading名前空間にあるので、コードの最初にImportsステートメントを記述して、名前空間を指定しておけば、「System.Threading.Thread」と書かなくても、単に「Thread」と書くことができるようになる。
スレッドの作成は、ほかのクラスのオブジェクトを作成する方法とまったく同じ。 と がそのコードだ。 では、Threadクラスのオブジェクトを参照する変数myThread1を宣言し、 ではNew演算子を使ってThreadクラスの新しいオブジェクト(インスタンス)を作成している。オブジェクトの参照はmyThread1に代入されているので、このmyThread1という変数を使えば、作成された新しいスレッドの操作ができることになる。
注意すべきはThreadコンストラクタの引数に指定した「AddressOf WithDraw」という個所。このAddressOfというのは、プロシージャの「デリゲート」を作成するというもの……といわれても、まったくイメージがわかないだろう。デリゲートとは日本語にすると「委任」といった意味になるが、その意味から考えても余計に分からなくなるのでやめておいた方がいい。
いまはデリゲートという難しい用語にあまり惑わされず、単純に割り切って「関数の参照」とでも理解しておくといいだろう。「AddressOf WithDraw」を直訳すれば「WithDrawプロシージャのアドレス」となるので、その方が直感的に分かりやすいはずだ。
もう少し厳密にいうと、デリゲートとは、ほかのプロシージャを間接的に呼び出すために使われるオブジェクトのことで、AddressOf演算子を使うと、そのオブジェクトの参照が得られる、ということになるのだが、要するに、そのスレッドで実行したいプロシージャの名前をAddressOfの後に書いておけばよいということだ。なお、これ以降はこの新しいスレッドのことを「WithDrawスレッド」と呼ぶことにしよう。
スレッドの開始にはStartメソッドを使う。Startメソッドに引数を指定して、スレッドとして実行されるプロシージャにその引数を渡すこともできるが、それについては後で触れることとしよう。むしろ、ここでは、最初に述べた「正しい結果は得られない」ということを思い出してほしい。
このプログラムでは、スレッドを開始した後、すぐにlblBalanceというラベル・コントロールのTextプロパティに新しい残高を設定している。が、残念ながら、このまま実行しても、古い残高しか表示されない。というのは、スレッドは同時に(非同期)に実行されるからだ。
先ほどから見てきたように、引き落としを実行するWithDrawスレッドは「時間のかかる処理」であり、少なくとも5秒はかかる。従って、残高の更新が終わる前に、残高が表示されてしまうというわけだ。図3のようなイメージでとらえるといいだろう。
図3 スレッドは非同期に実行される |
WithDrawスレッドの実行には5秒かかる。WithDrawスレッドがモタモタしている間に、メインのスレッドで、更新前の値がラベルに表示されてしまう。 |
このような問題を解決するにはどうすればいいだろうか。簡単に思いつくのは、WithDrawスレッドの方の最後に、
lblBalance.Text = decBalance.ToString
というコードを記述し、新しい残高をラベル・コントロールに表示するというものだが、呼び出し側のコントロールなどを別のスレッド(ここではWithDrawスレッド)から利用しようとすると、スレッド間の呼び出しに関するエラーが発生してしまう。一般に、フォーム上でユーザー・インターフェイス部品として使われるコントロールはほかのスレッドからアクセスすることはできないので、残念ながらこれは却下だ。
最も簡単な方法は、スレッドの実行が終了するのを待つという方法。その場合、WithDrawスレッドが実行されている間、メインのスレッドの実行を一時停止するという方法か、ThreadクラスのJoinメソッドを利用するという方法のいずれかが使える。
メインのスレッドの実行を一時停止するなら、WithDrawスレッドを開始した後に、スレッドの実行状態を表すIsAliveプロパティを調べるようなループを入れればよい。
| |
新しいスレッドの終了を待つコード | |
IsAliveプロパティがTrueであれば、スレッドが実行中なので、その間はSleepメソッドを使って待っているとよい |
Joinメソッドを使っても同じことができる。Joinメソッドを利用すれば、スレッドの実行が終了するまで、呼び出し側のスレッドを停止させておける。その場合は、上のコードのDo 〜 Loopの部分を、
myThread1.Join()
の1行に書き換えるだけでよい。いずれの方法でも、WithDrawスレッドの実行が終わってから、ラベルのテキストが変わるので、正しく30000と表示されるようになる*1。
*1 ただし、待ち状態になるとメイン・スレッドの操作ができなくなる。それを避け、WithDrawスレッドから、呼び出し元スレッドのlblBalanceコントロールに安全に(スレッドセーフに)アクセスするには、以下のような方法を使う。このようにすれば、メインのスレッドはブロックされない。ただし、本稿の目的のレベルを大きく超えているので、方法を個条書きで示すだけにとどめる。
[ソリューションエクスプローラ]の[すべてのファイルを表示]ボタンをクリックし、[Form1.Designer.vb]のコードを表示する クラスの定義の先頭に以下のコードを入力する。プロシージャ名は任意。
WithDrawプロシージャを以下のように書き換える
詳しくは、「.NET TIPS:Windowsフォームで別スレッドからコントロールを操作するには?」などを参考にしていただきたい。 |
サンプル・プログラム16の拡張 − 作成したスレッドにデータを渡す
スレッド間でデータをやりとりする方法にもいくつかの方法があるが、ここでは最も簡単な方法を示しておこう。
ThreadクラスのStartメソッドには、Object型の変数を引数として指定することができ、スレッドとして実行されるプロシージャでは、その値を引数として受け取ることができる。
例えば、サンプル・プログラム16では、預入額(または引出額)をフォーム・レベルの変数にして、いずれのプロシージャからも利用しているが、WithDrawプロシージャの中でこの値を変更することは普通あり得ないので、これを引数として渡せるようにしてみよう。コードは以下のようになる。
| ||||||||||||
新しいスレッドを実行するときにデータを渡すコード | ||||||||||||
|
変更したコードは4カ所だけ。変数decDepositは引数として渡されるので、フォーム・レベルの変数にしておく必要はない。そこで、 のようにmainProcプロシージャのローカルな変数にしておいた。スレッドを開始するときに、 で、decDepositを新しいスレッドに渡していることが分かるだろう。
ではWithDrawプロシージャの引数を書き換えている。これまで、引数はなかったが、値渡しのObject型の引数を1つ記述しておく。引数の名前は任意だが、参照渡しにしたり、ほかのデータ型を指定するとエラーになることに注意。最後の では、渡された値をDecimal型に変換し、残高を計算している。渡された値はObject型なので、CType関数によるデータ型の変換が必要になるというわけだ。
INDEX | ||
連載:VB 6ユーザーのための<BR>これならマスターできるVB 2005超入門 | ||
第11回 初めてのマルチスレッドと排他制御入門 | ||
1.サンプル・プログラム16 − 初めてのマルチスレッド・プログラム(1) | ||
2.サンプル・プログラム16 − 初めてのマルチスレッド・プログラム(2) | ||
3.サンプル・プログラム17 − Mutexによる排他制御 | ||
4.Mutexを利用した排他制御を組み込む | ||
「これならマスターできるVB 2005超入門」 |
- 第2回 簡潔なコーディングのために (2017/7/26)
ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている - 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう - 第1回 明瞭なコーディングのために (2017/7/19)
C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える - Presentation Translator (2017/7/18)
Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|
- - PR -