質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

4回答

4755閲覧

2つのスレッドの処理時間について

nobysanz

総合スコア42

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

0グッド

0クリップ

投稿2015/12/03 23:20

編集2015/12/07 12:25

お世話になります。
スレッドの優先度について質問です。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(スレッドA①フォーム作成完了 new Form) Thread Run --:06:59:37.1103348(スレッドA②処理) Thread Run --:06:59:37.1103348(スレッドA②処理) Thread Run --:06:59:37.1103348(スレッドA②処理) Thread Run --:06:59:37.1103348(スレッドA②処理) Thread Run --:06:59:37.1103348(スレッドA③フォーム表示 Application.Run) Thread Run --:06:59:37.1259348(スレッドB①フォーム作成完了 new Form) Thread Run --:06:59:37.1259348(スレッドB②処理) Thread Run --:06:59:37.1259348(スレッドB②処理) Thread Run --:06:59:37.1259348(スレッドB②処理) Thread Run --:06:59:37.1259348(スレッドB②処理) Thread Run --:06:59:37.1259348(スレッドB③フォーム表示 Application.Run)

2015-12-07 追記です(コメントありがとうございます!)
このようなことをしているのは、本質問の発端となった事象を再現させたく。
検証しておりました。https://teratail.com/questions/21087
私の認識と、再現させるには以下と考えますがいかがでしょうか?

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) ファイルアクセス処理をする → 検証中

ほかになにか考えられますでしょうか?

ご教授お願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

こんにちは。
なんか少しディープですね…

以下予想です。
ご存じかもしれませんが、背景としてWindowsフォームのコントロールはUIスレッドからしか操作できません。

その関係でフォームの生成時には、WindowsFormsSynchronizationContextの設定が走ります。
おそらく、そのタイミングでTheadContext.MarshalingControlにアクセスするんですが
こいつがスレッドセーフな作りになっているのでスレッド間で待機し合うのではないかと。

<参考>
WindowsFormsSynchronizationContext.Constructor
http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/WindowsFormsSynchronizationContext.cs,d37865bac8deb498

Application.TheadContext.MarshalingControl
http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Application.cs,150fc91c7f8e7176

ということで、私の予想では
質問者さんの推察どおり、「フォーム生成時にリソースロックしているため。」です。
間違ってたらすみません!

投稿2015/12/04 01:39

編集2015/12/04 02:10
Tak1wa

総合スコア4791

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

eripong

2015/12/04 02:06

質問と関係ありませんが、改行については、以下の通りのようです。 https://teratail.com/blog/article/ba21 > 任意に改行を行う場合は末尾に半角スペース2個を挿入ください。また空白行は詰まります。
Tak1wa

2015/12/04 02:11

ありがとうございます。改行調整してみました。
nobysanz

2015/12/04 04:08

コメントありがとうございます! 内容確認します。
guest

0

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のバージョンを上げてみるくらいしか思いつきませんでした。

投稿2015/12/04 03:24

編集2015/12/12 05:59
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Chironian

2015/12/04 04:53

なるほど。「メッセージ・ループを回してくれるような生成方法で生成」されているのですね。失礼しました。 それを前提に、回答へ追記します。
guest

0

マルチコアで特に排他制御をすることもなく同時に開始した処理が、15ミリ秒以上待たされるということはあまり考えられません。

スレッドプールで同じキューに入ってしまった場合は、先に実行したスレッドの処理が終わる(制御を返す)までは次のスレッドの実行は待たされるので、ご質問にあるような現象が発生する場合があります。

ただ、キューは複数持っているはずなので、同じキューに入るということも通常考えられないのですが。

スレッドプールのキューの数を制限したりはしていませんか?


追記

問題を切り分けるために。試しにスレッドプールではなく通常のThreadクラスを使ってみてはどうでしょうか。
(というか、フォームのように元々作成コストが高い処理の場合はスレッドプールを使うメリットがあまりないので、通常のThreadクラスで問題ないと思います。むしろ負荷の高い処理でキューを消費するのはもったいない気がします。)

投稿2015/12/04 01:19

編集2015/12/04 02:02
catsforepaw

総合スコア5938

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nobysanz

2015/12/04 04:09

コメントありがとうございます! スレッドに変更して試してみます。
guest

0

あり得る結果ですが、排他処理がかかっているわけではありません。
スレッドは走り出すとしばらくはCPUを掴み続ける傾向があります。タスクスイッチは重いオーバーヘッドなのでそんなに頻繁にはかからないのです。
さてオブジェクトのnewには内部的に排他制御がかかっているのは想像がつくと思います。空いているメモリ領域を探して見つけたら確保して、という操作が競合してはまずいですからね。
これによりスレッドBは待機状態に入ります。

タスクスイッチがそう頻繁には起こらないせいでAは走り続けBはしばらく止まったままになり、ログのような結果になります。
しかしそうなることは保証されていません。

強制的にタスクスイッチを起こすこともできます。Thread.Yield()を呼ぶことで、待機中のスレッドがいればそちらにCPU使用権を渡せます。

投稿2015/12/03 23:45

yuba

総合スコア5568

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nobysanz

2015/12/04 00:13

コメントありがとうございます。頻繁にタスクスイッチはされないのですね。 動作PCは、4コアのプロセッサですが、スレッドA、スレッドBが、各コアに割り振られた場合は、並列で動作することは、考えられますでしょうか? ご教授お願いいたします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問