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

わんくま同盟

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

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


(過去ログ 18 を表示中)
■6874 / )  Re[3]: Control.Invokeが使えない件。
□投稿者/ NyaRuRu (18回)-(2007/08/25(Sat) 15:59:59)
2007/08/25(Sat) 16:05:57 編集(投稿者)
2007/08/25(Sat) 16:00:19 編集(投稿者)
■No6864 (れい さん) に返信
> 2007/08/25(Sat) 09:37:52 編集(投稿者)
> 
> 
>>テストコードはhttp://bbs.wankuma.com/index.cgi?mode=al2&namber=6760の改造で詳細は後述。
> 
> .Net1.1用コードは以下。
> .Net2.0でもInherits以外同じ。
> なんか怪しい感じだけどツッコミは禁止。

このコードで確かにハングはするので,
無理矢理時間を作って調べてみましたが,見た感じ以下の描像とは異なるケースでしたよ.
もしかしたら下のケースもあるのかもしれませんが,10 回試して 10 回とも同じ理由でしたが,
下のような問題ではありませんでした.
環境は Windows Vista Ultimate Edition / Intel Core Duo T2500 です.

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

実際にはメインスレッドのWaitHandle待ちは最大 1 秒のタイムアウトが設定されていて,
実はハングしたように見えて 1 秒ごとに Wait から復帰はしています.
無限に待っているわけではありませんでした.
ただ,間違った条件でぐるぐる 1 秒ごとに待っているので,
見た目無限に待機しているように見えるようです.

前回も書いたように,Win32 Window は何かのスレッドに所属しています.
これは GetWindowThreadProcessId API で調べることができます.
で,ある Win32 スレッドから,WinForms のコントロールに Invoke を行うとき,
次のような処理が行われるようです.

1) Win32 的な GetCurrentThread と,
   相手のコントロールが所属する Win32 スレッドの ID が一致するかどうか?
2) 一致しなければ,スレッド間通信を開始し(PostMessage) レスポンス待ちループへ.
2.0) レスポンス待ちループ
2.1) 相手のコントロールが所属する Win32 スレッドを取得
2.2) 相手のコントロールが所属する Win32 スレッドが終了しているかチェック.
2.2.1) 終了していればタイムアウト時間 1 msec で一回だけ応答を待って,
       応答があればリターン,タイムアウトなら Invoke 失敗
2.2.2) 終了していなければ,タイムアウト時間 1000 msec で応答を待って,
       応答があればリターン,タイムアウトなら 2.2 に戻る

さて,(1) と (2.1) で二回「相手のコントロールが所属する Win32 スレッドの ID」が
取得されていますが,この間に別スレッドで相手のコントロールが破棄されると,
返される ID が変わってしまうようです.
具体的な実装としては,コントロールが破棄されると,
「コントロールが所属する Win32 スレッドの ID」として GetCurrentThreadId API の値
を使うようでした.
HWND は既に無効なので GetWindowThreadProcessId が使えないから
こうしてるんでしょうかね.今回の場合明らかにまずいですが.

つまり何が起きるかというと,(1) の判定時はスレッド間通信が必要と見えて
レスポンス待ちループに突入してしまうのですが,
(2.1) の段階では相手のコントロールは GetCurrentThreadId に所属すると思ってしまう
わけです.
(2.2) の判定は,現在自分自身が走っているスレッドの生存判定になってしまい,
それが死んでいるわけもなく,結局 (2.2) と (2.2.2) を無限に行き来すると.
とりあえず私のところで起きていたのはそんな感じの問題に見えました.
端的に言えば,スレッド終了条件のチェックが間違っているということになります.

れいさんのお話によれば他にもハングするパターンがあるのかもしれませんが,
少なくともタイムアウトなしの WaitHandle で無限に待つというという実装で
無かったことは確かです.

参考までに,
http://bbs.wankuma.com/index.cgi?mode=al2&namber=6843#19
のコードを前半だけ書き換えてみたものを示します.
本来,別スレッドで表示された ChildForm を,メインスレッドから見れば,InvokeRequired は常に true なはずなのですが,
破棄のタイミングでこれが false に変化してしまうことがあるのが分かります.
最初 true だったタイミングである処理に入り,
その後 false になって別の情報を受け取ると,破綻することがあると.

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
            If Not ChildForm.InvokeRequired Then
                MessageBox.Show("InvokeRequired = false!")
            End If
        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

申し訳ありませんが,私の方は時間的にこれが限界なので,後はそれっぽい人にお任せします.
Feedback を上げるかインシデントを使えば,
少なくとも今回のパターンについては修正を検討してもらえるかと.

その他のパターンは結局遭遇できなかったのでよくわかりませんでした.

返信 編集キー/


管理者用

- Child Tree -