none
delegate基礎 別スレッドからComboboxのテキストを取得するには RRS feed

  • 質問

  • 前提

    • VisualBasic2013
    • Windows7 pro SP1 x64
    • Windows Formアプリケーション
    • .NET Framework 4

    シリアルポートの通信アプリケーションで、データ受信時に起動するスレッドからフォーム上に配置されたCombobox2の選択テキストを読み出すことができず困つてゐます。

    今回のソフトウェアは以前作成した通信ソフトウェアを元に改造するものです。
    この時は受信データをTextboxに表示するため、返し値のないプロシージャーをデリゲートから呼び出す処理をしてをり動作してゐました。

    今回はCombobox2.Textの読み出しと言ふことで

        Public Delegate Function textDelegate() As String

    と関数型のデリゲートを定義し、呼び出し箇所で

       Dim dlgt As textDelegate = New textDelegate(AddressOf Combobox2.Text)
       Dim text As String = dlgt()

    としましたがCombobox2.Textの部分に下線がつきエラーになりビルドできません。

       メソッド 'Public Overrides Property Text As String' に、
      デリゲート 'Delegate Function textDelegate() As String'
      と互換性があるシグネチャがありません

    デリゲート呼び出し時にComboBox2.Textを直に指定するのが悪いのかと、下記combotext()の関数を定義して間接的記法を実験しました。しかしビルドできるものの動作時に別スレッドからの呼び出しエラーとなり解決に至りません。

     Private Function combotext() As String
            Return ComboBox2.Text
        End Function

    何を考へ違ひしてゐるのかヒントをいただきたく回答をお待ちしてをります。

    以下にコードの構造を示します。
    Public Class Form1

      省略

        '//デリゲートの宣言
        Public Delegate Sub MyDelegate(ByVal msg() As Byte)
        Public Delegate Function textDelegate() As String

      省略 

        'serialPort1のDataReceivedイベント
        Private Sub SerialPort1_DataReceived(ByVal sender As System.Object, _
                                             ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived

        省略
                packet_parse()
        省略

        End Sub


        Private Sub packet_parse()

        省略
                Dim dlgt As textDelegate = New textDelegate(AddressOf Combobox2.Text)
                Dim text As String = dlgt()
        省略

        End Sub

        省略

    End Class

    2019年2月24日 8:42

回答

  • 基本的に別スレッドからコントロールに対して取得・設定の操作をすることはできないように設計されています。
    この原則に対して、SerialPort の DataReceived イベントは別スレッドから実行されるため、コントロールの Invoke メソッドを経由することになります。

    参考例
    https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q12104816777
    https://kana-soft.com/tech/sample_0008_4.htm

    ただ、Invoke を使って呼び出し元と連携するコード(戻り値をもらって処理する)というのは、ラムダ式が絡むなど、難しいかと思いますので、イベントが来た途端、すべての処理を Invoke でメインスレッドにお願いする形を考えてみてはいかがでしょうか。
    (Packet_parse 自体を Invoke で呼ぶということ)

    2019年2月24日 8:49
    モデレータ
  • プロパティでは、そのままではメソッドにはなりませんので、デリゲートとすることはできません。
    ラムダ式を書くか、「Text プロパティから取得した String を返す」メソッドを自分で別途書くかのいずれかが必要です。

    // あえて旧仮名使いをするのは読みづらくするだけなのでやめた方が良いと思いますが・・・。

    • 回答としてマーク kusanagi42 2019年2月25日 14:18
    2019年2月25日 13:05
    モデレータ

すべての返信

  • 基本的に別スレッドからコントロールに対して取得・設定の操作をすることはできないように設計されています。
    この原則に対して、SerialPort の DataReceived イベントは別スレッドから実行されるため、コントロールの Invoke メソッドを経由することになります。

    参考例
    https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q12104816777
    https://kana-soft.com/tech/sample_0008_4.htm

    ただ、Invoke を使って呼び出し元と連携するコード(戻り値をもらって処理する)というのは、ラムダ式が絡むなど、難しいかと思いますので、イベントが来た途端、すべての処理を Invoke でメインスレッドにお願いする形を考えてみてはいかがでしょうか。
    (Packet_parse 自体を Invoke で呼ぶということ)

    2019年2月24日 8:49
    モデレータ
  • .NET Frameworkのプログラミングにおいて、全てに型があります。変数を定義する場合、必ず型が必要です。
    では、関数を入れるための変数であるデリゲートの型は何でしょうか? 関数の定義は無限にあります。よって、事前に型を用意することができません。よって、デリゲートという型をプログラマが定義して用意します。
    これで関数の型の定義ができ、関数を入れるための変数を用意できました。この変数を経由してスレッド間が跨げます。
    この変数に入れた関数をスレッドから呼び出すために、Invokeを使用します。当然ながら変数(デリゲート)に関数をセットしたスレッドではInvokeは必要ありません。Invokeが必要かどうかを判定するメソッドであるInvokeRequiredも用意されています。
    スレッド間を跨いで実行することをマーシャリングといいます。マーシャリングを行うために、Invoke、BeginInvoke、EndInvokeが用意されています。


    ★良い回答には質問者は回答済みマークを、閲覧者は投票を!


    2019年2月24日 11:21
    モデレータ
  • 返信ありがたうございます。

    私はinvokeではなくdelegateでの処理を、過去動作させたコードがさうでしたので、考へてゐます。
    別スレッドからメインスレッドのコントロールを操作する例は見られます。
    良くある例はメソッドの実行です。しかし今回私がしたいやうなプロパティを読み出す例は見つけられませんでした。

    Packet_parse処理をメインスレッドに置くアイディアはありますね。参考にさせてもらひます。
    2019年2月25日 7:28
  • 返信ありがたうございます。

    私はCombobox.Textを読みだす場合の関数型を

      function(void) As String

    と考へてdelegateを

      Public Delegate Function textDelegate() As String

    と用意しましたがこれは間違ひですか。

    2019年2月25日 7:34
  • voidが無指定という意味であれば、それで合っています。deleagate、つまり関数の型は、関数の戻り値の型、およびパラメーター、およびパラメータの型で決まるからです。

    ★良い回答には質問者は回答済みマークを、閲覧者は投票を!

    2019年2月25日 10:01
    モデレータ
  • はい。

    ではCombobox.Textを読み出す場合の型と一致したdelegateを準備して呼び出してゐるのに

      メソッド 'Public Overrides Property Text As String' に、
      デリゲート 'Delegate Function textDelegate() As String'
      と互換性があるシグネチャがありません

    といふエラーが発生するのでせう。それが分からないのです。

    2019年2月25日 11:17
  • プロパティでは、そのままではメソッドにはなりませんので、デリゲートとすることはできません。
    ラムダ式を書くか、「Text プロパティから取得した String を返す」メソッドを自分で別途書くかのいずれかが必要です。

    // あえて旧仮名使いをするのは読みづらくするだけなのでやめた方が良いと思いますが・・・。

    • 回答としてマーク kusanagi42 2019年2月25日 14:18
    2019年2月25日 13:05
    モデレータ
  • プロパティはメソッドではない、その通りです。もう少し研究してみます。

    ありがたうございました。

    なほ仮名遣ひは公私とも十二年間正仮名遣ひを使つてゐますが支障がないので悪しからず。

    2019年2月25日 14:20
  • コントロールを別スレッドから操作すると InvalidOperationException エラーが発生しますが、.NET Framework 1.x には、この制限はありませんでした。

    2.0 からエラーが発生するようになったのですが、互換性を保つためのプロパティ Control.CheckForIllegalCrossThreadCalls を False にすると、エラーが発生しなくなります。

    GDI 関連を別スレッドから操作すると問題が発生することがありますが、Text プロパティの読み出しや書き込みは、メッセージキューを経由してシリアライズされるため問題ないと思います。

    Control.CheckForIllegalCrossThreadCalls = False
    Dim text As String = ComboBox2.Text
    Control.CheckForIllegalCrossThreadCalls = True
    
    真っ当な方法ならばこんな感じでしょうか。
    Dim invoker As Func(Of String) = Function()
                                         Return ComboBox1.Text
                                     End Function
    Dim text As String = DirectCast(Me.Invoke(invoker), String)
    2019年2月25日 16:15