注意この記事の最新版は「.NETのマルチスレッドプログラミング」で公開しています。†マルチスレッドプログラミングで注意すべき様々な問題について、第21号の「スレッドの同期」などで説明してきました。GUIのWindowsアプリケーションでは、これらに加えて、さらに気をつけるべきことがあります。いうのは、コントロール(フォームも含む)のメソッドは、そのコントロールを作成したスレッドからのみ呼び出される必要があるからです(注1)。(これは、Windowsフォームがシングルスレッドアパートメント(STA)モデルを使用しているためです。コントロールのサンプル」等をご覧ください。)コントロールを作成したスレッド以外のスレッドからそのコントロールのメソッドを呼び出すには、マーシャリングにより、コントロールを作成したスレッドからメソッドを呼び出します。このマーシャリングには多くのリソースを消費しますが、これを確実且つ効率よく行う方法として、ControlクラスにはInvoke、BeginInvoke、およびEndInvokeメソッドが用意されています。コントロールのサンプル(注1:ただし、Invoke、BeginInvoke、EndInvoke、CreateGraphicsメソッドの呼び出しはスレッドセーフであり、その必要がありません。)まずはInvokeメソッドの使用法を説明します。Invokeメソッドを使う際の手順は、コントロールのメソッドを呼び出すためのメソッドを作成する。そのメソッドに合ったデリゲートを宣言する。コントロール(あるいはコントロールの親フォーム)のInvokeメソッドをデリゲートオブジェクトを指定して呼び出す。いったようになります。次にInvokeメソッドを使った具体例を示します。ここでは、Button1をクリックすることにより新しいスレッドを作成し、このスレッドによりTextBox1に0から99までの数を順番に表示させています。このコードはFormクラス内に書かれているものとします。TextBox1.Lines.Length;}上記の例では、TextBox1を作成したスレッドはメインスレッドですので、それ以外のスレッド"t"からTextBox1の内容を変更するために、Invokeメソッドを使用しています。Invokeメソッドを通じてAddStringメソッドを呼び出すことにより、AddStringメソッドはTextBox1を作成したメインメソッドで実行されるようになります。次にBeginInvoke、EndInvokeメソッドを使ってみましょう。Invokeメソッドが同期的に実行するのに対して、BeginInvokeメソッドは非同期的に実行します。EndInvokeメソッドはBeginInvokeメソッドにより実行されたメソッドの返り値を取得するために使用します。BeginInvokeメソッドを呼び出したときに非同期操作が終了していなければ、終了するまでブロックされます。先ほどの例をBeginInvoke、EndInvokeメソッドを使って書き換えてみましょう。DoSomethingメソッド以外に変更はありませんので、DoSomethingメソッドのみ示します。}}変わったのはInvokeの代わりにBeginInvokeとEndInvokeを使用している所だけです(BeginInvokeの後すぐにEndInvokeを呼び出しているため、非同期の意味がありませんが)。BeginInvokeを呼び出したときにIAsyncResultオブジェクトを取得し、EndInvokeで返り値を取得するために使用します。返り値を取得する必要が無ければ、EndInvokeを呼び出す必要はありません。あるコントロールに対してInvokeメソッドを使ってメソッドを呼び出す必要があるか調べる方法として、Control.InvokeRequiredプロパティが用意されています。そのコントロールを作成したスレッドがInvokeRequiredを呼び出したスレッドと異なる場合はTrue、それ以外はFalseを返します。InvokeRequiredプロパティを調べ、Invokeメソッドを使う必要があればInvokeを使い、そうでなければ使わないでメソッドを呼び出すコード例を以下に示します。ここでもDoSomethingメソッドのみ示し、それ以外は前と同じです。}}このようなコードを書くことにより、DoSomethingメソッドはどのスレッドから呼び出しても適切にInvokeの使用を切り替えることができるようになります。TextBox1のあるフォームのInvokeメソッドを呼び出しても結構です(むしろその方が良いケースも多いでしょう)。†スレッドプールを使ってスレッドを開始する方法では、スレッドとデータのやり取りをする方法が用意されていますが、Thread.Startメソッドでスレッドを開始した場合、そのスレッドへデータを渡したり、スレッドからデータを取得することは簡単ではありません。Thread.Startメソッドに必要なThreadStartデリゲートには引数も戻り値もありませんので、引数や戻り値を持つメソッドによりスレッドを開始することは出来ないからです。そこで、別の方法を考える必要があります。考えられるのが、データを渡す側、取得する側両方のスレッドからアクセスできる変数を使ってデータのやり取りするという方法です。この方法でマルチスレッドメソッドから戻り値を取得するには、戻り値を返すスレッドがその結果を変数に確実に代入したことをその戻り値を取得するスレッドの側で知る必要があります。下に示したサンプルコードでは、Joinメソッドを使って戻り値を返すスレッドが終わるまで待機してからその結果を取得しています。このコードでは、MyThreadメソッドを別スレッドで開始し、sleepTimeフィールドを介してMyThreadメソッドにデータを渡し、returnValueフィールドを介してMyThreadメソッドの結果を渡すようにしています。プロシージャのパラメータと戻り値」では、別スレッドとして実行するメソッドはクラスにラップし、パラメータとして機能するフィールドをそのクラスに定義するのが「最善の方法」であると述べられています。これに従って先のコードを書き直すと次のようになります。}}別スレッドから戻り値を取得するための方法としては、コールバックメソッドを使う方法もあります。この場合は上記の例のようにJoinメソッドでスレッドの終了を待機する必要がありません。この方法では、データを渡すスレッドからデータを受け取るスレッドのメソッドをデリゲート(またはイベント)により呼び出し、その引数として結果を渡します。次の例は上記のコードをさらに書き換え、別スレッドで実行されるSpendLongTimeメソッドからコールバックメソッドGetCallbackResultを呼び出すことにより結果を返すようにしています。当然ですが、GetCallbackResultはメインスレッドではなく、別スレッドで実行されることに注意してください。