DOBON.NETプログラミング道掲示板
(現在 過去ログ2 を表示中)

[ 最新記事及び返信フォームをトピックトップへ ]

■33783 / inTopicNo.1)  マルチスレッドにおける画面の更新
  
□投稿者/ たこ 一般人(6回)-(2018/01/18(Thu) 14:20:15)
  • アイコン環境/言語:[Windows10 64bit、VB.NET、.NET Framework 4.5.2、スーパーマップルデジタル18西日本版+SDK] 
    分類:[.NET] 

    いつも参考にさせて頂いており、お世話になっています。

    現在、ネット上よりAIS情報(位置情報)を取得し、船舶の位置を地図に描画するソフトを組んでいます。

    地図の再描画イベントで船舶の位置を再描画したいのですが、
    『 System.InvalidOperationException 』エラーが出て止まってしまいます。

    地図の再描画イベントで船舶の位置を再描画させなければちゃんと動くのですが、
    画像の様に再描画が遅れます(当然ですが…^^;

    何かマルチスレッドの組み方のアドバイス、エラーの回避方法など
    良い知恵をお貸し頂けないかと思い、投稿させて頂きました。


    <動作概要>
    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
    【通常時】ネットワーク(スレッド:非同期動作) → 位置抽出 → Shipクラスに登録し、ArrayListにする
          
            → 時間が経過した情報については船舶の破棄 → Shgipクラスの描画


    【地図の再描画イベント時】イベント → Shgipクラスの描画
    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜


    <ソースの概略>
    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
    Public Class ShipArray

      〜〜〜 中略 〜〜〜

    Public Sub DrawShip()
    If ShipsLock Then Exit Sub               '追加・削除時にロックが掛かっている時は描画しない
    For Each sp As Ship In ships             
    sp.setPosition()                  '緯度・経度からX,Yを計算し、描画する ← ※イベントで描画させるとここでエラーが出る。
    Next
    End Sub

    End Class

    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
    Class Ship
    Private WithEvents shipImage As New PictureBox

      〜〜〜 中略 〜〜〜

    Private Sub setPosition()
          <地図の描画範囲(緯度・経度)から地図上のX,Yを計算>
    position = New Point(地図上のX, 地図上のY)
    Dim pnt = New Point(shipImage.Width / 2.0F, shipImage.Height / 2.0F)
    mF.Invoke(New posInvoker(AddressOf privateSetShipPos), New Object() {position - pnt})
    mF.Invoke(New visInvoker(AddressOf shipImgVisible), New Object() {True})
    End If
    End Sub

    Private Sub privateSetShipPos(ByVal pos As Point)
    shipImage.Location = pos
    End Sub

    Private Sub shipImgVisible(ByVal visible As Boolean)
    shipImage.Visible = visible
    End Sub

    End Class
    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

    Class frmMain

      〜〜〜 中略 〜〜〜

    Private Sub MpgMap_Paint(sender As Object, e As PaintEventArgs) Handles MpgMap.Paint
    If Not MpgMap.Drawable() Then Exit Sub
    'main.DrawShip()                                      ← コメントを外すとShipArrayクラスでエラー
    End Sub


    End Class
    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜


    <参考にしたサイト・記事>
    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
    .NETマルチスレッドプログラミング 1:スレッドの実行と同期
      https://codezine.jp/article/detail/135?p=2
    時間のかかる処理の進行状況を表示する
      https://dobon.net/vb/dotnet/programing/displayprogress.html#backgroundworker
    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

    見づらくなりましたが、よろしくお願い致します。
引用返信 削除キー/
■33784 / inTopicNo.2)  Re[1]: マルチスレッドにおける画面の更新
□投稿者/ Azulean 大御所(492回)-(2018/01/18(Thu) 22:15:35)
  • アイコンInvalidOperationException だけではなく、同時に表示されるメッセージも書いた方が良いでしょう。

    予想としては、描画中に ships の配列・リストに要素が追加、要素の削除などの編集が起きているのではありませんか?
    「列挙中にコレクションが変更されました」というメッセージであればそうです。

    DrawShip の For Each 実行中に別スレッドから ships を変更されないようにきちんと排他する(ロックする・保護する)か、
    列挙するコレクションのコピーを作ってください。

    ※「追加・削除時にロックが掛かっている時は描画しない」とあるが、「描画中に追加・削除できないようにする」とはなっていないので排他できていません。
引用返信 削除キー/
■33785 / inTopicNo.3)  Re[2]: マルチスレッドにおける画面の更新
□投稿者/ たこ 一般人(7回)-(2018/01/18(Thu) 23:22:27)
  • アイコン
    No33784に返信(Azuleanさんの記事)
    
    お返事ありがとうございます。
    
    > InvalidOperationException だけではなく、同時に表示されるメッセージも書いた方が良いでしょう。
    
    なるほどです。すいませんでした。
    ----------------------------------------------------------------
    例外がスローされました: 'System.InvalidOperationException' (mscorlib.dll の中)
    型 'System.InvalidOperationException' のハンドルされていない例外が mscorlib.dll で発生しました
    コレクションが変更されました。列挙操作は実行されない可能性があります。
    ----------------------------------------------------------------
    
    
    > ※「追加・削除時にロックが掛かっている時は描画しない」とあるが、「描画中に追加・削除できないようにする」とはなっていないので排他できていません。
    
    なるほどです…
    描画中に追加・削除が出来る事を忘れていました…
    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
      Public Sub DrawShip()
        If ShipsLock Then Exit Sub               '追加・削除時にロックが掛かっている時は描画しない
            ShipsLock = True                    '描画中はロックを掛ける  ← ※追加
        For Each sp As Ship In ships              
          sp.setPosition()                  '緯度・経度からX,Yを計算し、描画する ← ※イベントで描画させるとここでエラーが出る。
        Next
            ShipsLock = False                   'ロックを外す       ← ※追加
      End Sub
    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
    
    これで上手く行きました!
    
    マルチスレッドの考え方は解っては…いるつもりなのですが、
    実際どの部分にロックを掛けなければいけないかと言うのが
    いまいち良く解っていません^^;
    参考になるサイトを見つけながらいろいろ勉強していきたいと思います。
    
    ありがとうございました。

解決み!
引用返信 削除キー/
■33786 / inTopicNo.4)  Re[3]: マルチスレッドにおける画面の更新
□投稿者/ Azulean 大御所(493回)-(2018/01/19(Fri) 06:28:39)
  • アイコンNo33785に返信(たこさんの記事)
    >     If ShipsLock Then Exit Sub               '追加・削除時にロックが掛かっている時は描画しない
    > ShipsLock = True                    '描画中はロックを掛ける  ← ※追加

    この処理は弱点があります。
    もし、ShipsLock = False の状態で、2つのスレッドが同時に If の行を実行することがあると、どちらもチェックを通過する可能性があります。
    そのため、「ごくまれに落ちる」といった不具合につながります。

    SyncLock ステートメントを利用してもらった方が安全だと思われます。
    http://www.atmarkit.co.jp/ait/articles/0505/25/news113.html
解決み!
引用返信 削除キー/
■33788 / inTopicNo.5)  Re[4]: マルチスレッドにおける画面の更新
□投稿者/ たこ 一般人(8回)-(2018/01/19(Fri) 15:00:33)
  • アイコン
    2018/01/19(Fri) 15:01:06 編集(投稿者)
    
    ■No33786に返信(Azuleanさんの記事)
    
    重ね重ねのご参考、ありがとうございます。
    元々VB6.0でプログラムを組んでいたので、
    オブジェクトとかスレッドの意味があまり良くわかっていないせいもあるとは思いますが
    ロックの掛け方についてはまだまだ勉強が足りず、
    いろいろ検索するのですが、『 ロックオブジェクト 』
    としか書いてない記事がほとんどで、
    どうしてそのオブジェクトでロックが掛けられるのか
    また、どのオブジェクトで掛けるのが一番良いのかを
    いまいち理解していません^^;
    
    例えば、
    SyncLock Me
    SyncLock ArrayList.SyncRoot
    
    ご紹介頂いた記事(http://www.atmarkit.co.jp/ait/articles/0505/25/news113.html)内の
    SyncLock Bank
    
    上記3つは何となくは解るのですが、
    ロックオブジェクトを新たに作ってロックする…
    (今回もこの方法でやっていますが…)
    と言うのがいまいちピンと来ていません^^;
    ただ、いろんな番所からロックを掛けられるのは便利なのかな…と言う認識です。
    (Public Propertyにすればの話)
    
    この記事を書きながら考えたのですが、
    1つのオブジェクトに多数のメソッドがある場合、
    SyncLock Meでロックしてしまうと、
    ロックしたオブジェクトの他のメソッドもロックが掛かり、
    アクセス出来なくなるので、下の様なロックを掛ける…
    
    …と言う認識でよろしいでしょうか^^?
    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
        Private ShipsLockObject As Object = New Object()
        Private _ShipsLock As Boolean
        Private Property ShipsLock As Boolean
            Set(value As Boolean)
                SyncLock ShipsLockObject
                    _ShipsLock = value
                End SyncLock
            End Set
            Get
                SyncLock ShipsLockObject
                    Return _ShipsLock
                End SyncLock
            End Get
        End Property
    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
    
    ただ、最終的には
    
    >DrawShip の For Each 実行中に別スレッドから ships を変更されないようにきちんと排他する(ロックする・保護する)か、
    >列挙するコレクションのコピーを作ってください。
    
    との事だったので、船舶情報を途切れなく受信すると描画されないと思い、
    コレクションのコピーを作りました。
    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
    #Region "登録されている全船舶を返す"
        ''' <summary>
        ''' 登録されている全船舶を返す
        ''' </summary>
        ''' <returns>全船舶(Ships)</returns>
        Public Function getShips() As ArrayList
            Console.WriteLine("Execute ShipArray.getShips() As ArrayList")
            Return Ships.Clone
        End Function
    #End Region
    
        Public Sub DrawShip()
            Console.WriteLine("Execute ShipArray.DrawShip()")
            Dim sps As New ArrayList()
            sps = getShips()
            For Each sp As Ship In sps
                sp.ShipInvisible()
            Next
            For Each sp As Ship In sps
                sp.checkPosition()
            Next
            Dim cnt As Integer = 0
            For Each s As Ship In sps
                If s.getShipImageVisible Then cnt += 1
            Next
            mF.TS_ShipCount.Text = cnt
            sps = Nothing
        End Sub
    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
    
    オブジェクト指向もマルチスレッドも奥が深すぎです><

解決み!
引用返信 削除キー/



トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

Mode/  Pass/


- Child Tree -