C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[17]: Control.Invokeが使えない件。


(過去ログ 18 を表示中)

[トピック内 52 記事 (1 - 20 表示)]  << 0 | 1 | 2 >>

■6843 / inTopicNo.1)  Control.Invokeが使えない件。
  
□投稿者/ れい (58回)-(2007/08/24(Fri) 19:16:28)

分類:[.NET 全般] 

こちら(http://bbs.wankuma.com/index.cgi?mode=al2&namber=6760)で話してたんですが、
皆さんの意見を聞きたいので、タイトルと内容を合わせてスレッド立てます。

タイトルの通り、「Control.Invokeが使えない」件に関して、
私の事実誤認であるとか、同意であるとか、皆さんの意見をください。

Control.InvokeやEndInvokeを何も考えずに使うと、
Controlを廃棄した際にデッドロックします。
Invokeがウィンドウメッセージを送り、返事を待ってる間に
Controlが廃棄された場合、いつまで待っても返事が返ってこないからです。
これは
http://osdir.com/ml/windows.devel.dotnet.clr/2004-04/msg00157.html
http://www.microsoft.com/japan/msdn/community/gdn/ShowPost-26227.htm
などなど、かなり既出の件です。

この対策をいろいろ考えたのですが、
Control.Invokeを安全に使える方法が全く存在せず、
Invokeを使うこと自体が間違いであるという結論に至ってしまいました。

ControlやFormはいつ廃棄されるのか、
Invokeする側(Invoker)からはわかりません。
Invokeされる側(Invokee)でフラグを立てて回避しようとしても、
Invokeメソッド内で実際にウィンドウメッセージを送るまでの間に
ControlやFormが死んでしまうとデッドロックします。

WorkerThreadをInvokerとする場合も同様で、
WorkerThreadが死ぬ前にControlが死ぬ可能性のある場合は、
WorkerThreadがInvokeでデッドロックし、残ってしまいます。
Controlは、WM_DESTROYやWM_QUITで、いつでも死ぬ可能性があり、
Controlが死ぬ前にWorkerThreadが死ぬのを保証するには、Abortし終了を待つしかありませんが、
その間にInvokeされてる場合もありうるのでダメです。

いろいろ考えましたが、どうやってもControl.Invokeを安全には使えませんでした。
代替方法はBeginInvokeを投げっぱなしにするか、EndInvokeをタイムアウト付きで呼ぶことでした。

ですが、
http://msdn2.microsoft.com/ja-jp/library/757y83z4(VS.80).aspx
http://www.atmarkit.co.jp/fdotnet/dotnettips/312ctrlinvoke/ctrlinvoke.html
http://codezine.jp/a/article.aspx?aid=139#invoke
http://dobon.net/vb/dotnet/programing/progressdialog.html
などなど、
ネット上にも、MSDNにもInvokeを使ったサンプルはたくさんあります。
見る限りどれもデッドロックを引き起こすコードで、
いくつかは検証してみましたが、実際にデッドロックを引き起こしました。
MSDNのサンプルでさえ、Formを閉じたときにスレッドがWaitのまま残りました。

私が検証した限りでは、安全に使うことはできませんでしたし、
理論上できないのではないかと私は思っています。
もし安全に呼ぶ方法が一つでもあるなら存在意義がありますが、
一つも無いなら、ただ邪魔なだけのメソッドです。

だれか発言力のある偉い人が、「Control.Invokeはダメ」って言ってくれれば安心するんですが、
見当たりません。
私一人が思ってるだけ、言ってるだけでは、自分自身も説得できなくて。
誰か偉い人、「ダメ」って言ってくれませんか?

また、「きちんと使えてる。お前のチエが猫並みなだけ」って人がいるなら、
是非猫にチエをご教授ください。
引用返信 編集キー/
■6846 / inTopicNo.2)  Re[1]: Control.Invokeが使えない件。
□投稿者/ NyaRuRu (11回)-(2007/08/24(Fri) 23:10:15)
2007/08/24(Fri) 23:14:17 編集(投稿者)
No6843 (れい さん) に返信
> Control.InvokeやEndInvokeを何も考えずに使うと、
> Controlを廃棄した際にデッドロックします。
> Invokeがウィンドウメッセージを送り、返事を待ってる間に
> Controlが廃棄された場合、いつまで待っても返事が返ってこないからです。

ほんとかなと思って試してみましたが,
以下のケースでは閉じたフォームに対する Invoke で InvalidOperationException 例外が発生しました.
以下 C# ですが,Form を作って Load イベントを次のように設定してください.

private void Form1_Load(object sender, EventArgs e)
{
    Button button = new Button();
    button.Click +=
        delegate(object _sender, EventArgs _e)
        {
            Form frm = new Form();
            frm.Show();
            Thread thread = new Thread(delegate()
            {
               try
               {
                   while (true)
                   {
                       frm.Invoke(
                           (MethodInvoker)
                           delegate { frm.Text = DateTime.Now.ToString(); });
                       Thread.Sleep(1000);
                   }
               }
               catch (Exception exp)
               {
                   MessageBox.Show(exp.Message);
               }
            });
            thread.Start();
        };
    this.Controls.Add(button);
}
ボタンを押すとスレッドが起動され,
表示用フォームのタイトルを現在時刻で更新していきます.

Windows のメッセージキューはスレッドごとに独立しており,
ひとつのメッセージキューに複数のウィンドウが所属します.
これを称して,「ウィンドウ1はスレッドXに所有される」みたいな感じになると.
・スレッドX
    メッセージポンプ
      ウィンドウ1
      ウィンドウ2
      ウィンドウ3
が,http://bbs.wankuma.com/index.cgi?mode=al2&namber=6760 では
別スレッドでウィンドウを開いていますよね? あれって
・スレッドX
    メッセージポンプ
      ウィンドウ1
・スレッドY
    メッセージポンプ
      ウィンドウ2
みたいなことをしたいということなのでしょうか?
(と,れいさんに聞いても仕方がないと思いますが)

引用返信 編集キー/
■6847 / inTopicNo.3)  Re[2]: Control.Invokeが使えない件。
□投稿者/ 渋木宏明(ひどり) (308回)-(2007/08/24(Fri) 23:46:58)
渋木宏明(ひどり) さんの Web サイト
> ほんとかなと思って試してみましたが,
> 以下のケースでは閉じたフォームに対する Invoke で InvalidOperationException 例外が発生しました.

やっぱりそうですよねぇ。

Control.Invoke() が最終的に SendMessage() を呼び出しているなら、存在しないウィンドウへの SendMessage() はブロックではなくエラーになるはずで、その時例外が発生するというのは(とりあえずは)納得できる実装です。

Control.Invoke() で立ち往生するケースがあるということですが、SDK レベルで SendMessage() を使っていても、使い方がまずければデッドロックします。

なので、Control.Invoke() でデッドロックするのも似たような状況なんじゃないかなーと。

引用返信 編集キー/
■6849 / inTopicNo.4)  Re[2]: Control.Invokeが使えない件。
□投稿者/ れい (59回)-(2007/08/25(Sat) 02:34:12)
2007/08/25(Sat) 02:48:14 編集(投稿者)
No6846 (NyaRuRu さん) に返信
> 2007/08/24(Fri) 23:14:17 編集(投稿者)
> ■No6843 (れい さん) に返信
>>Control.InvokeやEndInvokeを何も考えずに使うと、
>>Controlを廃棄した際にデッドロックします。
>
> ほんとかなと思って試してみましたが,
> 以下のケースでは閉じたフォームに対する Invoke で InvalidOperationException 例外が発生しました.

スレッド間通信の問題ですので、一回だけでは再現しない場合もあります。
連続で何回も行ったり、高負荷時に試さなくてはいけません。
ダメな同期機構だと、開発時には大丈夫なのに、ある環境ではいつもダメ、
見たいなことがありますので、たくさん試して1回でもダメならダメです。
問題ないことの証明は激しく難しいですが。

適当な時間経ったら自動で閉じるFormをつくり、
延々とFormを作ってはInvoke、Formを作ってはInvoke、を繰り返して確認したところ…
Invokeで発生する可能性のある例外は、
・InvalidOperationException
・ObjectDisposedException
・System.ComponentModel.InvalidAsynchronousStateException(2.0のみ
でした。
また、600〜3000回に1回ほどInvokeでフリーズしました。
Invoke先で何もしないと3000回試してもフリーズしませんでした。
(最初は2回に1回くらいだったんですが、再起動したら減りました。)
環境とかで問題が発生する確率が大きく変わるので頻度は参考程度ですが、
http://bbs.wankuma.com/index.cgi?mode=al2&namber=6760 のコードでは
デッドロックが起こりえることは間違いありません。

> 別スレッドでウィンドウを開いていますよね? あれって
>略
> みたいなことをしたいということなのでしょうか?

あのスレッドではそうでしょう。
あんまりないですが、こういう仕様にしたいときもたまにあります。
Control.InvokeはGUIが無くても止まりますので、今回はあまり関係ないです。

No6847 (渋木宏明(ひどり) さん) に返信
> Control.Invoke() が最終的に SendMessage() を呼び出しているなら、存在しないウィンドウへの SendMessage() はブロックではなくエラーになるはずで、その時例外が発生するというのは(とりあえずは)納得できる実装です。

はい。おかしいときに例外は正しい。私もそれを期待します。
デッドロックは困る。

ちなみに、Control.InvokeはPostMessageを使っています。

> Control.Invoke() で立ち往生するケースがあるということですが、SDK レベルで SendMessage() を使っていても、使い方がまずければデッドロックします。
> なので、Control.Invoke() でデッドロックするのも似たような状況なんじゃないかなーと。

変な使い方をすればうまく動かないってのはなんでもそうなので、いいと思います。
Control.Invoke()でデッドロックしないコードが見当たらないのが
問題ではないかと。

Invoke先で何もしなければ今のところデッドロックは起こしませんが、
何もしなければInvokeの意味がないですし、
本当にデッドロックしないかもわかりません。
もしかしたら条件によってはロックするかも知れません。

どういうInvoke内容だったら安全なんでしょうか?
MSDNをいくら読んでも、どこを探しても、明確な指針が見当たりません。
それで皆さんの意見を聞きたかったのです。

話の内容からすると、
Nyaruruさんも渋木宏明(ひどり)さんも、
Control.Invokeのデッドロックに遭遇したことがないようですね。

以前問題を見つけたときはほぼ毎回デッドロックしてたんですが、
今調べてみると、たまにしか止まらなくって検証が大変です。
はてさて、どうしたものか。
引用返信 編集キー/
■6850 / inTopicNo.5)  Re[3]: Control.Invokeが使えない件。
□投稿者/ れい (60回)-(2007/08/25(Sat) 02:58:23)

あれ?
もしかしてこれか?

FIX: A Windows Forms-based application may stop responding when you make a call to the Control.Invoke method in the .NET Framework 1.1 SP1

http://support.microsoft.com/kb/896665/en-us

引用返信 編集キー/
■6851 / inTopicNo.6)  Re[3]: Control.Invokeが使えない件。
□投稿者/ 渋木宏明(ひどり) (309回)-(2007/08/25(Sat) 03:05:09)
渋木宏明(ひどり) さんの Web サイト
> スレッド間通信の問題ですので、一回だけでは再現しない場合もあります。

精密に内容を把握していなかったかも>自分

「Control.Invoke() の使用による不都合の原因は Windows のメッセージング機構に問題がある」という話だと思ってたんですが、そうではなくて「不都合の原因は Control.Invoke() の実装にあるのでは?」というお話なんですね?

とすると、ヘンテコな現象が起きる原因は

> ちなみに、Control.InvokeはPostMessageを使っています。

な気がします。

同期メッセージング用に用意されている SendMessage() を使わないで、非同期メッセージングを使って外側で、自前で同期を取ろうとしているところに無理が出てるのかもしれません。

> どういうInvoke内容だったら安全なんでしょうか?

ウィンドウやコモンコントロールのラッパに関して言えば、基本的には Control.Invoke() から戻ってくるまでの間、Control.Invoke() 先が有効なウィンドウハンドルを保持していればよろしんじゃないでしょうか。

もちろん、SendMessage() ですらデッドロックを起こすような変なことをしていないのが前提です。

> 話の内容からすると、
> Nyaruruさんも渋木宏明(ひどり)さんも、
> Control.Invokeのデッドロックに遭遇したことがないようですね。

無いです。

先にも述べたとおり、元々 SDK レベルで SendMessage() を使っている場合でさえ、条件が悪ければデッドロックを起こすことは分かっていますが、きわどいタイミングでメッセージ伝達が起きないように VC++ を多用していた頃からオブジェクトの寿命管理を徹底しています。

引用返信 編集キー/
■6852 / inTopicNo.7)  Re[3]: Control.Invokeが使えない件。
□投稿者/ NyaRuRu (12回)-(2007/08/25(Sat) 03:38:05)
No6849 (れい さん) に返信
> どういうInvoke内容だったら安全なんでしょうか?
> MSDNをいくら読んでも、どこを探しても、明確な指針が見当たりません。
> それで皆さんの意見を聞きたかったのです。

どんな範囲なら安全かどうかは必要になってから自分で調べます.
もちろん Win32 レベルで調べます.多分 .NET の層だけでは完結しないでしょう.
.NET の層だけで明確な指針を作りたいとは思いません.

> 話の内容からすると、
> Nyaruruさんも渋木宏明(ひどり)さんも、
> Control.Invokeのデッドロックに遭遇したことがないようですね。

> 以前問題を見つけたときはほぼ毎回デッドロックしてたんですが、
> 今調べてみると、たまにしか止まらなくって検証が大変です。
> はてさて、どうしたものか。

私の場合はあんまり GUI アプリケーションを作らないですからねぇ.
まあ目の前で起きれば調べますが.

あと,単にメッセージポンプが止まっただけのような状況を私はデッドロックとは呼んでこなかったのですが,今回の件をデッドロックと呼ぶのは混乱の元だったりしませんか?
(実際そのハング時に何が起きているのかちゃんと調べたわけじゃないですが)

引用返信 編集キー/
■6853 / inTopicNo.8)  Re[1]: Control.Invokeが使えない件。
□投稿者/ NyaRuRu (13回)-(2007/08/25(Sat) 03:46:51)
No6843 (れい さん) に返信
> Control.InvokeやEndInvokeを何も考えずに使うと、
> Controlを廃棄した際にデッドロックします。
> Invokeがウィンドウメッセージを送り、返事を待ってる間に
> Controlが廃棄された場合、いつまで待っても返事が返ってこないからです。

あー,ちゃんと読んでませんでした.
上の行で確定的に書かれていたから,廃棄後のコントロールで常にハングするのかと誤読していたのですが,Invokeとコントロール破棄が同時に起こったらという話なのですね.

まあそれでハングするのは確かにありそうな話です.
とりあえず中継用のメッセージ専用不可視ウィンドウでも作るんですかねぇ.
引用返信 編集キー/
■6854 / inTopicNo.9)  Re[3]: Control.Invokeが使えない件。
□投稿者/ NyaRuRu (14回)-(2007/08/25(Sat) 03:52:24)
No6849 (れい さん) に返信
>>別スレッドでウィンドウを開いていますよね? あれって
> >略
>>みたいなことをしたいということなのでしょうか?
>
> あのスレッドではそうでしょう。
> あんまりないですが、こういう仕様にしたいときもたまにあります。
> Control.InvokeはGUIが無くても止まりますので、今回はあまり関係ないです。

うーん,本当に関係ないんですかねぇ?
私がいいたいのは GUI あるなしじゃなくて,メインスレッドにおける Application.Run(new Form1()); がワーカースレッドにないことの影響とか,ちゃんと考えました? ということなのですが.

引用返信 編集キー/
■6855 / inTopicNo.10)  Re[4]: Control.Invokeが使えない件。
□投稿者/ 渋木宏明(ひどり) (310回)-(2007/08/25(Sat) 04:09:21)
渋木宏明(ひどり) さんの Web サイト
> あと,単にメッセージポンプが止まっただけのような状況を私はデッドロックとは呼んでこなかったのですが,今回の件をデッドロックと呼ぶのは混乱の元だったりしませんか?

例えば、ワーカスレッドでウィンドウを作ったとして、同じスレッドでメッセージポンプを回さないのはまずいすね。

Control.Invoke() の実装が SendMessage() ではなく PostMessage() を使用しているなら、メッセージ送信先でメッセージポンプが回っていなければメッセージキューが詰まった時点でブロックかな?

SendMessage() を使っていれば同期的に WndProc() が直接呼び出されるので、そういう「ふん詰まり」は原則おきませんが。

引用返信 編集キー/
■6856 / inTopicNo.11)  Re[3]: Control.Invokeが使えない件。
□投稿者/ 渋木宏明(ひどり) (311回)-(2007/08/25(Sat) 04:11:31)
渋木宏明(ひどり) さんの Web サイト
> Control.InvokeはGUIが無くても止まりますので、今回はあまり関係ないです。

GUI の無い Control.Invoke() ってどゆこと???

Control クラスは Windows.Forms 所属のクラスなので、Control 派生クラスを使って時点で GUI はそこにあると思うんですが。
(ユーザに見えるかどうかは別として)

引用返信 編集キー/
■6857 / inTopicNo.12)  Re[3]: Control.Invokeが使えない件。
□投稿者/ NyaRuRu (15回)-(2007/08/25(Sat) 04:41:56)
2007/08/25(Sat) 04:47:26 編集(投稿者)

No6849 (れい さん) に返信
> 環境とかで問題が発生する確率が大きく変わるので頻度は参考程度ですが、
> http://bbs.wankuma.com/index.cgi?mode=al2&namber=6760 のコードでは
> デッドロックが起こりえることは間違いありません。

確かに http://bbs.wankuma.com/index.cgi?mode=al2&namber=6760 のコードでフォーム2を閉じるとハングすることがあるのは再現しました.
が,アンマネージデバッグを有効にするで Win32 例外を一通り眺めてみると,ハング時にはファイナライザスレッドから RichEdit に飛んで COM リソースの解放をしているっぽくて,そこでアクセス違反が起きてましたよ.
以下コールスタックです.

riched20.dll!CNotifyMgr::Remove() + 0x5 bytes
riched20.dll!CTxtRange::~CTxtRange() + 0x2d bytes
riched20.dll!CTxtRange::`scalar deleting destructor'() + 0xd bytes
riched20.dll!CTxtRange::Release() + 0x1a bytes
mscorwks.dll!ReleaseTransitionHelper() + 0x5f bytes
mscorwks.dll!SafeReleaseHelper() + 0x6d bytes
mscorwks.dll!SafeRelease() + 0x2f bytes
mscorwks.dll!RCW::ReleaseAllInterfaces() + 0x53 bytes
mscorwks.dll!RCW::ReleaseAllInterfacesCallBack() + 0x96 bytes
mscorwks.dll!RCW::Cleanup() + 0x22 bytes
mscorwks.dll!RCWCleanupList::ReleaseRCWListRaw() + 0x14 bytes
mscorwks.dll!RCWCleanupList::ReleaseRCWListInCorrectCtx() + 0x4380 bytes
mscorwks.dll!RCWCleanupList::CleanupAllWrappers() + 0x10e733 bytes
mscorwks.dll!SyncBlockCache::CleanupSyncBlocks() - 0x5653c bytes
mscorwks.dll!Thread::DoExtraWorkForFinalizer() + 0xc9d13 bytes
mscorwks.dll!WKS::GCHeap::FinalizerThreadWorker() + 0xa8 bytes
mscorwks.dll!Thread::UserResumeThread() - 0x204ae2 bytes
mscorwks.dll!Thread::DoADCallBack() - 0x203b25 bytes
mscorwks.dll!Thread::DoADCallBack() - 0x203c00 bytes
mscorwks.dll!ManagedThreadBase_NoADTransition() + 0x32 bytes
mscorwks.dll!ManagedThreadBase::FinalizerBase() + 0xb bytes
mscorwks.dll!WKS::GCHeap::FinalizerThreadStart() + 0xa9 bytes
mscorwks.dll!Thread::intermediateThreadProc() + 0x46 bytes
kernel32.dll!@BaseThreadInitThunk@12() + 0x12 bytes
ntdll.dll!__RtlUserThreadStart@8() + 0x27 bytes

ちょっと月末締めの原稿書きが忙しいのでこれ以上調べるつもりはないですが,sos.dll 等を使えばその「デッドロック時」に何が起きているかもうちょっと調べられるはずなので,せめて待機中のハンドルが何であるかとか前後で変な (アンマネージ) 例外が起きていないかとか,調べた方がいいんじゃないですかね?
引用返信 編集キー/
■6858 / inTopicNo.13)  Re[4]: Control.Invokeが使えない件。
□投稿者/ れい (61回)-(2007/08/25(Sat) 04:48:28)
No6851 (渋木宏明(ひどり) さん) に返信
> 「Control.Invoke() の使用による不都合の原因は Windows のメッセージング機構に問題がある」という話だと思ってたんですが、そうではなくて「不都合の原因は Control.Invoke() の実装にあるのでは?」というお話なんですね?

そうです。
内部でAsyncWaitHandleをずっと待ち続けてるようなので、

No6852 (NyaRuRu さん) に返信
> あと,単にメッセージポンプが止まっただけのような状況を私はデッドロックとは呼んでこなかったのですが,今回の件をデッドロックと呼ぶのは混乱の元だったりしませんか?

ポンプが止まってるだけではないですが、
おっしゃるとおり、デッドロックではないですね。
ロック持ち合ってるわけじゃないですから。
ハングですね。

No6856 (渋木宏明(ひどり) さん) に返信
>>Control.InvokeはGUIが無くても止まりますので、今回はあまり関係ないです。
> GUI の無い Control.Invoke() ってどゆこと???

あっと、失礼。言葉が足りない。
Invokeされる側でなく、Invokeする側の話です。
Invokeする側はControlを持たないWorkerThreadでも、ハングします。

No6854 (NyaRuRu さん) に返信
>>Control.InvokeはGUIが無くても止まりますので、今回はあまり関係ないです。
> うーん,本当に関係ないんですかねぇ?
> 私がいいたいのは GUI あるなしじゃなくて,メインスレッドにおける Application.Run(new Form1()); がワーカースレッドにないことの影響とか,ちゃんと考えました? ということなのですが.

Appication.Runで何か特殊なことをやってるかもしれませんが…。
メインスレッドからサブスレッドのFormにInvokeしても、
サブスレッドからメインスレッドのFormにInvokeしても同じでしたから、
まぁあんまり関係ないかと思ってるんですが。
Application.Runが影響してるんだとするとかなり醜い実装ですねぇ。

No6851 (渋木宏明(ひどり) さん) に返信
> きわどいタイミングでメッセージ伝達が起きないように VC++ を多用していた頃からオブジェクトの寿命管理を徹底しています。

私も結局自分で同期機構を入れてスレッド間通信してしまってるんですが、
せっかくあるのに、使いようがないというのが何とも気になります。

No6855 (渋木宏明(ひどり) さん) に返信
>>あと,単にメッセージポンプが止まっただけのような状況を私はデッドロックとは呼んでこなかったのですが,今回の件をデッドロックと呼ぶのは混乱の元だったりしませんか?
> 例えば、ワーカスレッドでウィンドウを作ったとして、同じスレッドでメッセージポンプを回さないのはまずいすね。
> Control.Invoke() の実装が SendMessage() ではなく PostMessage() を使用しているなら、メッセージ送信先でメッセージポンプが回っていなければメッセージキューが詰まった時点でブロックかな?
> SendMessage() を使っていれば同期的に WndProc() が直接呼び出されるので、そういう「ふん詰まり」は原則おきませんが。

件のコードではShowDialogで回してます。
Control.Invokeの実装は間違いなくPostMessageです。

引用返信 編集キー/
■6859 / inTopicNo.14)  Re[4]: Control.Invokeが使えない件。
□投稿者/ れい (62回)-(2007/08/25(Sat) 04:57:04)
No6857 (NyaRuRu さん) に返信
> 確かに http://bbs.wankuma.com/index.cgi?mode=al2&namber=6760 のコードでフォーム2を閉じるとハングすることがあるのは再現しました.
> が,アンマネージデバッグを有効にするで Win32 例外を一通り眺めてみると,ハング時にはファイナライザスレッドから RichEdit に飛んで COM リソースの解放をしているっぽくて,そこでアクセス違反が起きてましたよ.

私もRichEdit周りでなぜかエラーを出るのは確認してます。
が、そこで例外がおきなくてもハングする場合があります。
実は、例のコードをずっと回してると結構いろいろな例外が起きます。
Form.ResumeLayoutあたりでも発生します。

RichEditのエラーなども気になったんですが、
VirtualPCとかRDPとかだったんでとりあえず放置して、
TextBoxに変更して回してますが、やはりたまに止まります。

今とりあえず http://support.microsoft.com/kb/896665/en-us を調べてます。

引用返信 編集キー/
■6860 / inTopicNo.15)  Re[4]: Control.Invokeが使えない件。
□投稿者/ れい (63回)-(2007/08/25(Sat) 05:11:02)
No6851 (渋木宏明(ひどり) さん) に返信
>>どういうInvoke内容だったら安全なんでしょうか?
>
> ウィンドウやコモンコントロールのラッパに関して言えば、基本的には Control.Invoke() から戻ってくるまでの間、Control.Invoke() 先が有効なウィンドウハンドルを保持していればよろしんじゃないでしょうか。

普通そう思いますよね。
ですが、Invokeから戻るまでの間、Windowを保持するのが、結構難しい。
Invokeされてる場合はすぐに終了せずに、ClosingでCancelを返して…
ってなことを、別の同期機構を入れて処理しなきゃならんのです。
できなくはないと思うんですが、
メッセージループ部も、Controlの終了処理部分も隠蔽されてるので、
自分で違う通信方法を作ったほうがよっぽど早くて確実って話になってしまいます。

私も自分で実装してしまいましたし、
渋木宏明(ひどり)さんやNyaruruさんも自分でやってしまうとは思うんですが。

引用返信 編集キー/
■6861 / inTopicNo.16)  Re[5]: Control.Invokeが使えない件。
□投稿者/ NyaRuRu (16回)-(2007/08/25(Sat) 05:12:56)
2007/08/25(Sat) 05:24:22 編集(投稿者)

No6858 (れい さん) に返信
> Appication.Runで何か特殊なことをやってるかもしれませんが…。
> メインスレッドからサブスレッドのFormにInvokeしても、
> サブスレッドからメインスレッドのFormにInvokeしても同じでしたから、
> まぁあんまり関係ないかと思ってるんですが。
> Application.Runが影響してるんだとするとかなり醜い実装ですねぇ。

いや,PostMessage ってことならなおさら Win32 のメッセージ通信なわけで,DispatchMessage がいつどこでどのように起きるかは意識しないとまずいでしょう.
その辺をすっとばして,コントロールの寿命のみに注目して再現条件を考えて良いのかどうか,私にはまだよく分かりません.(まあこれは私が GUI アプリをそんなに作らないからですが)
.NET のプログラミングモデル上は「メインスレッドからサブスレッドのFormにInvoke」しているように見えても,実際のメッセージキューはスレッドごとにしか存在せず,従ってこれはフォーム間通信ではなくてスレッド間通信だと考えるのが妥当だと思います.

なお,WPF の資料ですが,
http://msdn2.microsoft.com/ja-jp/library/ms741870(vs.80).aspx#stumbling_points
の「再入およびロック」のところでも解説されているように,GUI アプリケーションではかなり広範囲に Win32 メッセージの影響を考えないといけないので,出発点からして「綺麗」とはほど遠い状況です.
引用返信 編集キー/
■6862 / inTopicNo.17)  Re[6]: Control.Invokeが使えない件。
□投稿者/ れい (64回)-(2007/08/25(Sat) 06:52:47)
2007/08/25(Sat) 06:54:27 編集(投稿者)

No6861 (NyaRuRu さん) に返信
>>まぁあんまり関係ないかと思ってるんですが。
> いや,PostMessage ってことならなおさら Win32 のメッセージ通信なわけで,DispatchMessage がいつどこでどのように起きるかは意識しないとまずいでしょう.
>その辺をすっとばして,コントロールの寿命のみに注目して再現条件を考えて良いのかどうか,私にはまだよく分かりません.(まあこれは私が GUI アプリをそんなに作らないからですが)
>.NET のプログラミングモデル上は「メインスレッドからサブスレッドのFormにInvoke」しているように見えても,実際のメッセージキューはスレッドごとにしか存在せず,従ってこれはフォーム間通信ではなくてスレッド間通信だと考えるのが妥当だと思います.

んーと、話が通じてないですかね。
メインスレッドはApplication.Run経由でどこかにあるメッセージループを回していて、
サブスレッドはShowDialog経由でどこかにあるメッセージループを回していますね。

例のコードの場合、

メインスレッドでInvokeすると、サブスレッドのキューに該当するHWNDをセットしたInvoke用メッセージを投げます。
メッセージ投げたら、WaitHandleで待ちます。
サブスレッドはShowDialogの中でキューからInvoke用メッセージを拾い、
HWNDに該当する窓にDispatchします。
窓のWndProc内でInvoke用のメッセージだと認識して、
メッセージのパラメータとかからデリゲートを作り、呼びます。
デリゲートの実行が終わったら、WaitHandleをSetして、
またメッセージループに戻ります。
で、何回もやると何処かでWaitHandleのSetを忘れてフリーズしちゃうわけです。

これが
> メインスレッドからサブスレッドのFormにInvokeしても、
の場合で、

逆の場合、
> サブスレッドからメインスレッドのFormにInvokeしても同じでしたから、
も同様で、
サブスレッド側ではShowDialogで回ってて、そこからメインスレッドの管理下にある窓にPostMessage、
メインスレッドはApplication.Run内でメッセージ拾って窓にDispatchして、実行してWaitHandle.Set。
これも何回もやるとフリーズ。

ちなみに、サブスレッドからサブスレッドの窓へInvokeしても、
同様にフリーズします。

DispatchMessageがある場所とPostMessageでのメッセージの流れを書くと、
・Application.Runの中 -> ShowDialogの中
・ShowDialogの中 -> Application.Runの中
・ShowDialogの中 -> ShowDialogの中
となります。

この3者で、同じようにフリーズしてるなら、問題は
・Controlの廃棄
・メッセージループの停止
辺りであろうと判断しても妥当だと思いますが、いかが?

というか、

> いや,PostMessage ってことならなおさら Win32 のメッセージ通信なわけで,DispatchMessage がいつどこでどのように起きるかは意識しないとまずいでしょう.
>その辺をすっとばして,コントロールの寿命のみに注目して再現条件を考えて良いのかどうか,私にはまだよく分かりません.(まあこれは私が GUI アプリをそんなに作らないからですが)

すっ飛ばして考えていたわけではなく、
上記のように意識していましたし、そのつもりで書いてたんですが、
伝わらなかったのでしょうか?
それとも、上記考慮では足りないですか?

引用返信 編集キー/
■6863 / inTopicNo.18)  Re[1]: Control.Invokeが使えない件。
□投稿者/ れい (65回)-(2007/08/25(Sat) 09:25:40)
2007/08/25(Sat) 09:33:09 編集(投稿者)

FIX: A Windows Forms-based application may stop responding when you make a call to the Control.Invoke method in the .NET Framework 1.1 SP1
http://support.microsoft.com/kb/896665/en-us

このパッチの有無によるフリーズの程度、および.Net2.0との比較を行いました。

テストコードはhttp://bbs.wankuma.com/index.cgi?mode=al2&namber=6760の改造で詳細は後述。
Formを何回閉じたらフリーズするか、
環境ごとに5回ほど繰り返して測定しました。

他のソフトはきにせず動かしていました。(VisualStudioとかIISとかいろいろ。)

ログイン環境 CPU OS Framework パッチ有無 停止頻度

RDP HyperThread 2003 Net1.1 無し 100%
VPC ? XP Net1.1 無し 1/2〜1/5

RDP SingleProcessor 2003 Net1.1 有り 1/10〜1/50
RDP 2xXeon 2003 Net1.1 有り 1/3〜1/10
RDP HyperThread 2003 Net1.1 有り 1/3〜1/20

RDP SingleProcessor 2003 Net2.0 - <1/10000
RDP HyperThread 2003 Net2.0 - 1/100〜1/500
RDP 2xXeon 2003 Net2.0 - 1/100〜1/500
Console CoreDuo Vista Net2.0 - 1/500〜1/10000
VPC ? XP Net2.0 - 1/10000


RDP= Remote Desktop
VPC= Virtual PC

まとめ。

KB896665のFixを当てないとNet1.1ではInvokeは全然使えません。
当てても、Net1.1ではInvokeが止まることがかなりあります。
.Net2.0ではあまり止まりませんでしたが、
無視できるレベルではありませんでした。

フリーズした際、該当プロセスのCPU使用率はほぼ0でした
一度にたくさん起動したり、負荷が重いと止まる確率が上がるような気がします。
プロセッサの数が増えると止まる確率が上がる気がします。

BeginInvokeは殆ど試してませんが、
.Net1.1でも2.0でも、10000回程度では止まりませんでした。

感想。

やっぱり、Control.Invokeは私には使えそうにないです…。
1/500で止まるのは見過ごせないし、
どう同期組んでいいのかわからなすぎる。

引用返信 編集キー/
■6864 / inTopicNo.19)  Re[2]: Control.Invokeが使えない件。
□投稿者/ れい (66回)-(2007/08/25(Sat) 09:34:28)
2007/08/25(Sat) 09:37:52 編集(投稿者)
> テストコードはhttp://bbs.wankuma.com/index.cgi?mode=al2&namber=6760の改造で詳細は後述。

.Net1.1用コードは以下。
.Net2.0でもInherits以外同じ。
なんか怪しい感じだけどツッコミは禁止。


Public Class Form1
	Inherits System.Windows.Forms.Form

	Public ChildForm As Form2
	Public InvokeIteration As Integer = 0
	Public FormCreateIteration As Integer = 0
	Public ChildFormCreated As Boolean
	Public Timer1 As System.Windows.Forms.Timer

	Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
		Me.components = New System.ComponentModel.Container
		Timer1 = New System.Windows.Forms.Timer(Me.components)
		Me.components.Add(Timer1)
		AddHandler Timer1.Tick, New EventHandler(AddressOf Timer1_Tick)
		Me.Timer1.Interval = 10
		Me.Timer1.Start()
	End Sub


	Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs)
		If ChildForm Is Nothing Then
			Dim subthread As New System.Threading.Thread(AddressOf Me.SubThreadProc)
			SyncLock Me
				ChildFormCreated = False
			End SyncLock
			subthread.Start()
			Me.Text = "Create"
			FormCreateIteration += 1
			InvokeIteration = 0
			While True
				SyncLock Me
					If ChildFormCreated Then Exit Sub
				End SyncLock
			End While
		End If
		InvokeIteration += 1
		Me.Text = FormCreateIteration.ToString & ":" & InvokeIteration.ToString
		Try
			ChildForm.WriteLine(FormCreateIteration & ":" & InvokeIteration.ToString)
		Catch ex As Exception
		End Try
	End Sub

	Private Sub SubThreadProc()
		ChildForm = New Form2
		ChildForm.MainForm = Me
		ChildForm.ShowDialog()
		ChildForm.Dispose()
		ChildForm = Nothing
	End Sub
End Class

Public Class Form2
    Inherits System.Windows.Forms.Form

	Private Delegate Sub WriteLineDelegate(ByVal text As String)
	Public MainForm As Form1
	Public TextBox1 As TextBox

	Public Sub WriteLine(ByVal text As String)
		If Me.InvokeRequired Then
			Me.Invoke(New WriteLineDelegate(AddressOf WriteLine), New Object() {text})
		Else
			Me.Text = text
			Me.TextBox1.Text = text
			Me.TextBox1.ScrollToCaret()
			Me.TextBox1.Focus()
		End If
	End Sub

	Private Sub Form2_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
		Me.components = New System.ComponentModel.Container
		Dim Timer1 As New System.Windows.Forms.Timer(Me.components)
		TextBox1 = New TextBox
		Me.components.Add(Timer1)
		Me.Controls.Add(TextBox1)
		AddHandler Timer1.Tick, New EventHandler(AddressOf Timer1_Tick)
		SyncLock MainForm
			MainForm.ChildFormCreated = True
		End SyncLock
		Timer1.Interval = New Random().Next(100, 1000)
		Timer1.Start()
	End Sub

	Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs)
		Me.Close()
	End Sub

End Class

引用返信 編集キー/
■6865 / inTopicNo.20)  Re[7]: Control.Invokeが使えない件。
 
□投稿者/ 渋木宏明(ひどり) (312回)-(2007/08/25(Sat) 10:04:27)
渋木宏明(ひどり) さんの Web サイト
うーん、結局原因がどこにあると主張(あるいは推察)しているのかよく分からないです。

> メインスレッドでInvokeすると、サブスレッドのキューに該当するHWNDをセットしたInvoke用メッセージを投げます。
> メッセージ投げたら、WaitHandleで待ちます。
> サブスレッドはShowDialogの中でキューからInvoke用メッセージを拾い、
> HWNDに該当する窓にDispatchします。
> 窓のWndProc内でInvoke用のメッセージだと認識して、
> メッセージのパラメータとかからデリゲートを作り、呼びます。
> デリゲートの実行が終わったら、WaitHandleをSetして、
> またメッセージループに戻ります。
> で、何回もやると何処かでWaitHandleのSetを忘れてフリーズしちゃうわけです。

とのことですが、どこにも「フォームを閉じる」「ダイアログを閉じる」って書いてないところをみると、ここでは Invoke() の投げ先のフォームなりコントロールは常態で生存しているんですよね?

であるならば、

> この3者で、同じようにフリーズしてるなら、問題は
> ・Controlの廃棄
> ・メッセージループの停止
> 辺りであろうと判断しても妥当だと思いますが、いかが?

と言われている「Controlの廃棄」は起きるはずも無く、「メッセージループの停止」も「ヘンなこと」をしていない限り起きないと思うんですけど?

結局、Control.Invoke() を使うことによる不都合は↓のどちらのパターンであるって話なんでしょう?

・Control.Invoke() の投げ先が正常な状態にあっても常に起き得る。
・Control.Invoke() の投げ先であるフォーム/コントロールが廃棄途上にある時に起きる。

引用返信 編集キー/

次の20件>
トピック内ページ移動 / << 0 | 1 | 2 >>

管理者用

- Child Tree -