none
RemoveHandlerでのイベントとイベント ハンドラの関連付けの解除ができていないようです。 RRS feed

  • 質問

  • 最近VBを始めたばかりの全くの素人ですがどうかよろしくお願いします。

    VBを使ってマイコンとのシリアル通信をしようと考えています。マイコンから受け取った値を逐一ラベルに表示しようとしたのですが、

    RemoveHandlerがうまくいっていないようです。

    コード:

    Public Class Form1

        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        End Sub

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

            If SerialPort1.IsOpen = False Then
                SerialPort1.Open()
            End If

            If Button1.Text = "開始" Then

                AddHandler SerialPort1.DataReceived, AddressOf datareceived_serial
                Button1.Text = "停止"

            Else        

                RemoveHandler SerialPort1.DataReceived, AddressOf datareceived_serial
                Button1.Text = "開始"

            End If
        End Sub


        Delegate Sub Labell()

        Public Sub datareceived_serial()

            Invoke(New Labell(AddressOf labelout))
        End Sub

        Sub labelout()
            Label1.Text = SerialPort1.ReadLine
        End Sub

    End Class

    シリアル通信の受信はうまくいくのですが、通信を終了しようとボタンをクリックするとフリーズしてしまいます。

    「 RemoveHandler SerialPort1.DataReceived, AddressOf datareceived_serial」の「AddressOf datareceived_serial」に緑色の波線が引かれ次のような警告がエラー一覧に現れます。

    「AddressOf' へのメソッド引数には、イベントのデリゲート型への厳密でない変換が必要です。したがって、'AddressOf' 式はこのコンテキストでは無効です。'AddressOf' 式を変数に割り当て、その変数を使用してハンドラーとしてメソッドを追加および削除してください。」

    一体どうすればよいのでしょうか?


    • 編集済み Mizuhox 2012年7月11日 16:42
    2012年7月11日 16:40

回答

  • そのConnectページには設計による(By Design)と書かれているので、AddHandlerできてもRemoveHandlerできないのは正しいのだと思います。厳密でないデリゲート変換にもAddHandlerについてしか言及されていません。RemoveHandlerについて言及されていないのは不親切ですが!

    内部的な事情を想像すると、SerialPort1.DataReceivedイベントが必要としている関数(の引数・戻り値)とdatareceived_serialとでは一致していません。そのため、AddHandler / AddressOfは引数・戻り値を一致させるためにラッパー関数を自動的に生成します。
    AddHandlerはそれで問題ないのですが、RemoveHandlerでも新たなラッパーを生成し、それをSerialPort1.DataReceivedイベントから取り除こうとします。取り除くことには成功しますが、それはAddHandler時に生成された関数とは別なので、そちらは残ったまま。
    結果として、イベントが発生するたびにdatareceived_serialが呼ばれ続ける、という構造になっているかと。

    回避策についてはtrapemiyaさんが書かれている通り、ラッパー関数を生成しなくても済むように正しい引数・戻り値とすることです。

    • 回答の候補に設定 山本春海 2012年8月2日 8:52
    • 回答としてマーク Mizuhox 2012年8月3日 9:33
    2012年7月12日 1:06

すべての返信

  • お使いのVisual Studioのバージョン、およびお使いの.NET Frameworkのバージョンは何でしょうか?
    今回の現象ですが、バグかもしれませんね。

    Relaxed Delegate Conversion and RemoveHandler
    http://connect.microsoft.com/VisualStudio/feedback/details/341021/relaxed-delegate-conversion-and-removehandler

    上のページの回避策にも書いてありますが、厳密でないデリゲートを使わず、引数をきちん指定するとうまく動くのではないかと思います。以下のようになるのかな?

    Private Sub dataReceived_serial(ByVal sender As Object,  ByVal e As SerialDataReceivedEventArgs)


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2012年7月12日 0:42
    モデレータ
  • そのConnectページには設計による(By Design)と書かれているので、AddHandlerできてもRemoveHandlerできないのは正しいのだと思います。厳密でないデリゲート変換にもAddHandlerについてしか言及されていません。RemoveHandlerについて言及されていないのは不親切ですが!

    内部的な事情を想像すると、SerialPort1.DataReceivedイベントが必要としている関数(の引数・戻り値)とdatareceived_serialとでは一致していません。そのため、AddHandler / AddressOfは引数・戻り値を一致させるためにラッパー関数を自動的に生成します。
    AddHandlerはそれで問題ないのですが、RemoveHandlerでも新たなラッパーを生成し、それをSerialPort1.DataReceivedイベントから取り除こうとします。取り除くことには成功しますが、それはAddHandler時に生成された関数とは別なので、そちらは残ったまま。
    結果として、イベントが発生するたびにdatareceived_serialが呼ばれ続ける、という構造になっているかと。

    回避策についてはtrapemiyaさんが書かれている通り、ラッパー関数を生成しなくても済むように正しい引数・戻り値とすることです。

    • 回答の候補に設定 山本春海 2012年8月2日 8:52
    • 回答としてマーク Mizuhox 2012年8月3日 9:33
    2012年7月12日 1:06
  • 佐祐理さん、フォローありがとうございます。ちゃんとConnectのページを読まず、バグだと思い込んでしまいました。いけませんね、反省です。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2012年7月12日 1:39
    モデレータ
  • 早速のお返事ありがとうございます。

    正直解説を読んでもさっぱりなのですが、とりあえず「Private Sub datareceived_serial()」を「Private Sub dataReceived_serial(ByVal sender As Object,  ByVal e As SerialDataReceivedEventArgs)」に変更したのですが、今度は次のようなエラーが出てきます。

    「型'SerialDataReceivedEventArgs' が定義されていません。」

    2012年7月12日 7:48
  • datareceived_serialはどこに書かれていますか? Imports System.IO.Portsが無いだけかもしれません。

    (参考)
    SerialDataReceivedEventArgs クラス
    http://msdn.microsoft.com/ja-jp/library/system.io.ports.serialdatareceivedeventargs(v=vs.100).aspx


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    • 回答の候補に設定 山本春海 2012年8月2日 8:52
    2012年7月12日 9:08
    モデレータ
  • 以下のようになっています

    ################################################

    Public Class Form1

        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        End Sub

        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click


            If SerialPort1.IsOpen = False Then
                SerialPort1.Open()
            End If

            If Button1.Text = "開始" Then
                AddHandler SerialPort1.DataReceived, AddressOf datareceived_serial
                Button1.Text = "停止"
            Else
                RemoveHandler SerialPort1.DataReceived, AddressOf datareceived_serial
                Button1.Text = "開始"
            End If
        End Sub


        Delegate Sub Labell()

        Private Sub datareceived_serial(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
            Invoke(New Labell(AddressOf labelout))
        End Sub

        Sub labelout()
            Label1.Text = SerialPort1.ReadLine
        End Sub

    End Class

    ################################################

    「Imports System.IO.Ports」というのは「Public Class Form1」の前に付け加える、ということでよいのでしょうか?




    • 編集済み Mizuhox 2012年7月12日 9:35
    2012年7月12日 9:33
  • 「Imports System.IO.Ports」というのは「Public Class Form1」の前に付け加える、ということでよいのでしょうか?

    その通りです。このようにしてもダメでしょうか?また、名前空間を省略せずに、以下のように書いた場合、どうなりますか?

    System.IO.Ports.SerialDataReceivedEventArgs


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2012年7月13日 0:35
    モデレータ
  • Imports System.IO.Ports
    Imports System.IO.Ports.SerialDataReceivedEventArgs

    の二つを記述し、エラーは消えたのですが、まだフリーズしてしまいます。

    ただ、フリーズしたプログラムをタスクマネージャで強制終了したあと次のようなエラーが出てきました。


    RemoveHandlerが原因だと思っていたのですが、この行をコメントアウトしてみたところフリーズはしなくなりました。

    これは一体どういうことなのでしょうか?また、当初の質問の内容と違ってきてしまっているようなのですが、新しくスレッドを立てるべきでしょうか?


    • 編集済み Mizuhox 2012年7月13日 4:51
    2012年7月13日 4:50