連載Windows業務アプリケーション開発 Q&A #6グレープシティ株式会社 八巻 雄哉2006/11/18 |
|
|
VS 2005で実行するとスレッド処理で例外が発生
Visual Studio .NET 2003(以下、VS 2003)で作成したWindowsアプリケーションをVisual Studio 2005(以下、VS 2005)へ移行する作業をしています。VS 2003のときは問題なく動作していたコードなのですが、VS 2005で実行すると、
「有効ではないスレッド間の操作: コントロールが作成されたスレッド以外のスレッドからコントロール '……' がアクセスされました。」
という例外が発生してしまいます。.NET Framework 2.0(以下、.NET 2.0)ではスレッド関係の処理に何か変更があったのでしょうか?
Windowsフォームのコントロールは、基本的に複数のスレッドからアクセスされることは考慮されていません(スレッドセーフではありません)。そのため、コントロールの操作は必ずそのコントロールを作成したスレッドから行うようにする必要があります。
このことは、.NET Framework 1.x(以下、.NET 1.x)でも.NET 2.0でも同じですが、.NET 2.0ではスレッドセーフでない方法によるコントロールへのアクセスが検出され、それを行った場合には例外が発生するようになりました。
では、このスレッドセーフでない方法によるコントロールへのアクセスとはどのようなものか、実際に確認してみましょう。
Windowsアプリケーションのプロジェクトを新規作成し、フォームにButtonコントロールとTextBoxコントロールをそれぞれ1つずつ配置して下記のコードを記述してください。ここでは言語としてVisual Basicを使用します。
|
|
リスト1 Windowsフォーム・コントロールのスレッドセーフでない呼び出し |
上記のコードを実行すると、VS 2003の場合には特に例外は発生しませんが、VS 2005で実行した場合には下記の例外が発生することを確認できるかと思います。
|
|
VS 2005でリスト1を実行した場合に発生する例外メッセージ |
上記のようなコードは、文法的に間違っているわけではありませんが、結果が保証されない行儀の悪いコードであるといえます。VS 2003では問題なく動いてしまっていたコードも、VS 2005では安全ではないコードとして警告されるようになったというわけです。
では、正しいコードはどのようになるのか、ここではInvokeメソッドを使う方法と.NET 2.0から追加されたBackgroundWorkerコンポーネントを使う方法の2つを紹介します。
■Invokeメソッドを使う方法(.NET 1.x、.NET 2.0)
|
WindowsフォームのコントロールにはInvokeメソッドが用意されており、このメソッドを使うことでコントロールに対する操作をコントロールの作成されたスレッドで実行させることができます。
先ほどのリスト1のコードを下記のように書き換えます。このコード例では、InvokeRequiredプロパティを使ってInvokeメソッドを呼び出す必要があるかどうかを判定するようにしておき、必要がある場合(そのコードが別のスレッドで実行されている場合)にはInvokeメソッドでもう一度SetTextメソッドを呼び出しています。
|
|
リスト2 Windowsフォーム・コントロールのInvokeメソッドを使ったスレッドセーフな呼び出し |
■BackgroundWorkerコンポーネントを使う方法(.NET 2.0のみ)
|
BackgroundWorkerコンポーネントを使えば、イベント・ハンドラとしてスレッド処理を書くことができます。面倒なデリゲートの作成もInvokeメソッドの呼び出しも必要なくなるので非常に便利です。
BackgroundWorkerコンポーネントには、下記の3つのイベントが用意されています。
|
||||||||
BackgroundWorkerコンポーネントで用意されている3つのイベント | ||||||||
このうち、DoWorkイベント・ハンドラとなるメソッドは別スレッドで実行されます。通常、このイベントを使って時間のかかる処理などを記述します。
ProgressChangedイベントは非同期操作の進行状況をユーザーに報告するために使用されるイベントで、そのイベント・ハンドラはコントロールが作成されたスレッドで実行されます。このため、このイベントを利用してWindowsフォーム・コントロールにアクセスする処理を非同期に実行することができます。
ツールボックスの[コンポーネント]タブからBackgroundWorkerコンポーネントを選択してフォームに追加し、プロパティ・ウィンドウでBackgroundWorkerコンポーネントのWorkerReportsProgressプロパティをTrueに設定します。このプロパティをTrueに設定することで、DoWorkイベント・ハンドラ内からReportProgressメソッドを呼び出してProgressChangedイベントを発生させることができます。
先ほどのコードを下記のように書き換えます。
|
|
リスト3 BackgroundWorkerコンポーネントを使ったスレッドセーフな呼び出し |
なお、操作しようとするコントロールのCheckForIllegalCrossThreadCallsプロパティの値をFalseに設定することで、VS 2005でもVS 2003で実行していたときと同じように例外を発生させないようにすることは可能です。ただし、コードの実行結果はまったく保証されませんので、根本的な解決にはなりません。
【参考】MSDN: 方法 : Windows フォーム コントロールのスレッド セーフな呼び出しを行う |
10ポイントに設定したはずのフォントが9.75ポイントに
Visual Basic 6.0やVisual Studio 2005で、フォームやコントロールのフォントの大きさを[フォント]ダイアログを使用して10ポイントに設定した場合、なぜか9.75ポイントに設定されてしまいます。なぜこのような動作となるのでしょうか?
確かに[フォント]ダイアログを使用してフォント・サイズを設定すると、なぜか設定した値から微妙にずれた値が設定されてしまうことがあります。一見すると間違った動作に思えるこの現象ですが、「ポイント」という単位のフォントが実際にはどのように画面に表示されるのかを理解することで、それが誤解であることが分かります。
まず、ポイントという単位はどのような単位なのでしょうか? 1ポイントは1/72インチと決められています。つまりフォントを10ポイントで設定した場合、そのフォントの大きさは10/72インチになります。
次に、この10/72インチのフォントをディスプレイに表示することを考えてみましょう。Windowsの画面はデフォルトでは96dpiに設定されています。「dpi」というのは「Dot Per Inch」の略ですので、96dpiの場合1/96インチのものが1ドットとして表示されるということになります。
では、10/72インチのフォントを96dpiの画面で表示すると何ドットになるのでしょう?
- 96 dpi × 10/72 インチ = 13.333333333333333333……
13.3333……と割り切れない値になってしまいます。つまり、10ポイントと設定しても、ディスプレイで正確に10ポイントの大きさを表現することはできないということになります。
では9.75ポイントの場合はどうでしょうか?
- 96 dpi × 9.75/72 インチ = 13
13ドットと割り切れる値になりました。
これらの結果から分かるように、[フォント]ダイアログがフォントのサイズを画面に表示できる適切な値に自動的に変換してくれていたというわけです。
ちなみに、画面を120dpiに変更し、[フォント]ダイアログからフォント・サイズを10ポイントに設定すると、今度は9.75ポイントではなく10.2ポイントに設定されます。
- 120 dpi × 10.2/72 インチ = 17
[フォント]ダイアログにとっては、「良かれと思ってしたことが…」といったところでしょうか。
八巻 雄哉(やまき ゆうや) 2003年グレープシティ入社。PowerToolsシリーズのテクニカル・サポートを担当する傍ら、製品開発やマーケティングにも従事。現在は.NETとPowerToolsシリーズ普及のため、エバンジェリストとして活動中。.NET Framework 3.0の中で一番興味があるのはWPF。http://d.hatena.ne.jp/Yamaki/にてBlogを公開中。 |
「Windows業務アプリケーション開発 Q&A」 |
- 第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 -