質問: お世話になります。 スレッドの優先度について質問です。C#で2つのスレッドでフォームを表示する処理を行っております。 2つのスレッドをスレッドプールから順番に起動し、処理時間をログファイルに出力しました。 結果を見ると、起動時間は同じなのに、必ずform生成後に、2番目のスレッドのform生成が完了しております。2つのスレッド間で、排他制御などされているのでしょうか?たとえば、form作成時はリソースの排他がかかるとか? スレッドAの処理の間に、スレッドBの処理がはいると考えておりました。 ご教授お願いします。 ``` Thread Run St:06:59:37.0947348(スレッドA開始) Thread Run St:06:59:37.0947348(スレッドB開始) Thread Run --:06:59:37.1103348(スレッドA1フォーム作成完了 new Form) Thread Run --:06:59:37.1103348(スレッドA2処理) Thread Run --:06:59:37.1103348(スレッドA2処理) Thread Run --:06:59:37.1103348(スレッドA2処理) Thread Run --:06:59:37.1103348(スレッドA2処理) Thread Run --:06:59:37.1103348(スレッドA3フォーム表示 Application.Run) Thread Run --:06:59:37.1259348(スレッドB1フォーム作成完了 new Form) Thread Run --:06:59:37.1259348(スレッドB2処理) Thread Run --:06:59:37.1259348(スレッドB2処理) Thread Run --:06:59:37.1259348(スレッドB2処理) Thread Run --:06:59:37.1259348(スレッドB2処理) Thread Run --:06:59:37.1259348(スレッドB3フォーム表示 Application.Run) ``` 2015-12-07 追記です(コメントありがとうございます!) このようなことをしているのは、本質問の発端となった事象を再現させたく。 検証しておりました。 私の認識と、再現させるには以下と考えますがいかがでしょうか? 1 コメントの内容にて以下のような認識を持ちました。 (1)Formの生成は、リソースがロックされる。 同じタイミングで実行した2つのスレッドA,B内でFormを生成すると、 スレッドAのForm生成後に、スレッドBのForm生成が実行される。 (2)オブジェクトのnewは内部的に排他制御がかかる。 スレッドAでnew処理を実行した場合、newが完了するまで、スレッドBが待機される。 (3)タスクスイッチが頻繁発生しない タスクスイッチは、オーバーヘッドがかかる処理のため、 数100msの処理時間ではタスクスイッチしない。 (4)スレッドプールで同じキューに入った場合は、スレッド完了までまたされる。 (5)マルチコアで特に排他制御をすることもなく同時に開始した処理が、 15ミリ秒以上待たされるということは考えにくい。 2 スレッドAのフォーム作成完了後の処理中に、スレッドBのフォーム作成を実行させるには? (1) Sleep()をいれる → だめでした。 (2) Thread.Yield()をいれる → だめでした。 (3) ファイルアクセス処理をする → 検証中 ほかになにか考えられますでしょうか? ご教授お願いいたします。
回答: > > C#で2つのスレッドでフォームを表示する処理 > > > これって普通にできるのでしたっけ? Windowsのウィンドウを別スレッドで生成し制御する場合、それぞれのスレッドでメッセージ・ループを回す必要があります。nobysanzさんがどのような方法でフォームを生成されたのか分かりませんが、メッセージ・ループを回してくれるような生成方法で生成されたでしょうか?(すいません、C#ではやったことないので、C#で出来るかどうかさえ分かりません。C/C++ではできますし、やったことありますが。) もし、そこまでされてないのであれば、それぞれのフォームはメイン・スレッドで生成される筈です。 でないと、メイン・スレッドで回っているメッセージ・ループがそのウィンド宛のメッセージを受け取れない筈です。 そして、C#の場合、「フォーム コントロールへのアクセスは、本質的にスレッド セーフではありません。」となってます。同じフォームのコントロールを別スレッドからアクセスする場合は排他制御等が必要と言う意味ですが、異なるフォーム同士でもフォームを管理しているデータが本当にスレッド安全なのか、少し心配です。 nobysanzさんがおっしゃっているような動きをするということは、C#処理系側でスレッド安全になるよう排他制御している可能性はあると思います。 【追記】 Application.Run()は「現在のスレッドで標準のアプリケーション メッセージ ループの実行を開始します。」と記載されてました。 つまり、ここでメッセージ・ループを回すので、それぞれのスレッドにてメッセージ・ループを回っていますね。 さて、普通にメッセージ・ループを回すだけであれば特に他のスレッドと同期させる必要はありません。 ということは、特に排他制御していないにも関わらず、スレッドAは起動から約15mSecで処理を開始し、スレッドBは起動から約30mSecで処理を開始していることになります。 まるでシングル・スレッド動作してますね。マルチコアでそれは考えにくいです。 ところで、new Form;は、C#のフォーム・クラスのインスタンスを生成します。ここでCreateWindow()されているかも知れませんが、まだメッセージ・ループを周り始めていませんので、実際のウィンドウの生成・描画は始まっていません。Run()内でウィンドウ・メッセージを処理しつつ、生成・描画が進行します。 もしも、new Form;でMutexを獲得し、Run()でウィンドウが本当に生成・描画されてから解放しているなんてことになっていたと仮定すると、説明が付きますね。 (同じスレッド内で複数new Form;する時、同じスレッドが既にMutexを獲得しているケースではブロックされませんから。) (スレッドA開始)と(スレッドB開始)はそれぞれのスレッド内で出力してますか? それとも、各スレッドを起動したスレッドで出力してますか? もし、後者なら、試しに各スレッドでnew Form;を行う前にもログ出力してみませんか? スレッドに制御が渡ってから、new Form;が完了するまでの時間が気になります。 もし、それが片方は15mSec、もう一方が30mSecで安定しているようでしたら、.NETが何か15mSec単位のタイムスケジュールに同期するような処理をnew Form;に仕込んでいることになります。 .NETは思わぬところで、異常な処理をしていることがありましたので、油断できません。 あっ、異常な処理で、思い出しました。その異常な処理で困ったのは.NETのファイル・ストリームでした。ファイルが大きくなると、アペンド処理が異常に遅くなるという本当に不可解な現象でした。Windows APIを直接呼び出して回避したことがあります。 もし、ログを同じファイルへファイル・ストリームを使って出力しているのでしたら、それが何か悪さしていることも考えられます。当然排他制御されてますし。 もし、同じファイルに出力しているようでしたら、別ファイルに出してみるのも手です。 でも、.NET経由なので安心はできませんが... 【余談】 なんか改行が使いにくくなってますね。>teratail って、 eripongさんが改行方法を書いてくれてました!! ありがとう。 【追記】 > > 2015-12-07 追記です > > (中略) > 2 スレッドAのフォーム作成完了後の処理中に、スレッドBのフォーム作成を実行させるには? > > に今気が付きました。 > > > もし、本当に.NETがフォーム生成中(new Form~フォーム表示)の間、排他制御していたら、無理な気がします。 確認するために、スレッドAのnew Formの**直前**でSleep(5)としてみて、スレッドAとスレッドBのフォーム作成タイミングが逆にならないでしょうか? もし、そうなるなら、これは回避できないように感じます。 後は.NETのバージョンを上げてみるくらいしか思いつきませんでした。