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

わんくま同盟

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

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


■88575 / )  Re[6]: フォームの連続的な書き換えに関して
□投稿者/ 魔界の仮面弁士 (1828回)-(2018/09/08(Sat) 18:58:12)
No88568 (パヨンパヨン  さん) に返信
> でも、この方法だと複数の引数を受け取れないようなのですが

受け渡せますよ?

下記の例で行くと、方法 1 がそれにあたりますが、
方法 2 や 3 でも同じ結果が得られます。

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Task.Factory.StartNew(
        Sub()
            For i = 1 To 100
                Dim x As String = i.ToString()
                Dim y As String = (100 - i).ToString()
                Dim z As Color = Color.FromArgb(255 * i \ 100, 128, 128)

                ''方法1
                Invoke(New Action(Of String, String, Color)(AddressOf 複数の引数), x, y, z)

                '方法2
                'Invoke(Sub() 複数の引数(x, y, z))

                ''方法3
                'Invoke(New MethodInvoker(
                '    Sub()
                '        TextBox1.Text = x
                '        TextBox2.Text = y
                '        TextBox1.BackColor = z
                '    End Sub))

                Thread.Sleep(10)
            Next
        End Sub)
End Sub

Private Sub 複数の引数(a As String, b As String, c As Color)
    TextBox1.Text = a
    TextBox2.Text = b
    TextBox1.BackColor = c
End Sub



> 以下のコードだとエラーになります

VB バージョンが不明な点も気になりますが、それには目を瞑るとしても、
どの部分で何というエラーになるのかぐらいは書いて欲しいです…。



> frm.BeginInvoke(Sub() SetText(Form1.TextBox3, i))

【問題点1】ラムダ式の遅延評価と変数のスコープ

まずは引数 i。上記の変数 i はループカウンタですが、
ラムダ式は遅延実行されるものであることに注意してください。
(VB コンパイラのバージョンによっては、これは BC43234 の警告となります)

たとえば、このようなラムダ式があったとします。

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim act(9) As MethodInvoker
        For i As Integer = 0 To 9
            act(i) = Sub() Debug.Print(i)
        Next

        Array.ForEach(act, Sub(method) method())
    End Sub

この場合、0, 1, 2, 3, …, 8, 9 が出力されるようにも思えますが、
実際には、すべて 10 が出力されることになるでしょう。

連番で出力されるためには、
  For i As Integer = 0 To 9
    act(i) = Sub() Debug.Print(i)
  Next
  Array.ForEach(act, Sub(method) method())
ではなく、
  For i As Integer = 0 To 9
    Dim 局所変数 As Integer = i
    act(i) = Sub() Debug.Print(局所変数)
  Next
  Array.ForEach(act, Sub(method) method())
とする必要があります。



> frm.BeginInvoke(Sub() SetText(Form1.TextBox3, i))

【問題点2】既定のフォームインスタンス Form1 と明示的インスタンス frm

Form1.TextBox3 を扱うために、何故 frm の BeginInvoke を呼び出しているのでしょうか。

TextBox3 を操作するために、TextBox3 (またはその親フォーム等)の
BeginInvoke/Invoke を呼ぶのではなく、それとは別のインスタンスで
あると予想される「frm 変数」のインスタンスに対して
実行依頼を投げている点が不自然に思えました。

UI スレッドが一つしか無い場合には、結果的には正しいスレッドで動くのですが、
それならばそもそも、わざわざ frm.BeginInvoke と書く必要は無く、
単に BeginInvoke で良いわけで。(Button2 から呼んでいるのですから)

あるいは逆に、SplashScreen 利用時などといった、
UI スレッドが複数あるようなケースだとしたら、UI スレッドでの操作のために、
TextBox3 (またはその親フォーム等)ではなく、別のインスタンスであると
予想される frm に対して実行依頼を投げているの点が不自然です。


それに、frm は「明示的にインスタンス化されたフォーム変数」であるように
見えますが、Form1 はそうではなく、いわゆる My.Forms.Form1 相当の
「既定のフォームインスタンス」では無いでしょうか。

もしそうなら、このコードの Button2_Click が Form1 内に書かれていた場合、
BC31139 のコンパイルエラーになりうる可能性があります。
いずれにせよ、既定のインスタンスに頼ったコードは避けた方が良いでしょう。
(複数のスレッドをまたぐような場面で使いたいのであれば尚の事)



> frm.BeginInvoke(Sub() SetText(Form1.TextBox3, i))

【問題点3】オブジェクトの管理

BeginInvoke メソッドや Invoke メソッドは、スレッドセーフであることが
保証されているため、どのスレッドから呼び出しても問題にはなりません。

しかし、Form1.TextBox3 というのは、
 Friend WithEvents TextBox3 As TextBox
というフィールド変数ですよね。
この変数へのアクセスはスレッドセーフになっていませんので、
望ましいコードとは言えません。



■No88567 (パヨンパヨン  さん) に返信
> Public Sub SetText(i As Integer)
>     Form1.TextBox3.Text = i.ToString()
> End Sub

「frm.BeginInvoke から呼ばれた SetText」は、
frm が管理されているスレッド上で動作しますから、
そこで呼び出された SetText 内から
Form1 の TextBox3 フィールドにアクセスするのは OK です。

返信 編集キー/


管理者用

- Child Tree -