none
有効ではないスレッド間の操作を行いたい。 RRS feed

  • 質問

  • ソース

        Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
            'Processオブジェクトを作成
            Dim p As New System.Diagnostics.Process()

            '出力とエラーをストリームに書き込むようにする
            p.StartInfo.UseShellExecute = False
            p.StartInfo.RedirectStandardOutput = True
            p.StartInfo.RedirectStandardError = True
            'OutputDataReceivedとErrorDataReceivedイベントハンドラを追加
            AddHandler p.OutputDataReceived, AddressOf p_OutputDataReceived
            AddHandler p.ErrorDataReceived, AddressOf p_ErrorDataReceived

            p.StartInfo.FileName = _
                System.Environment.GetEnvironmentVariable("ComSpec")
            p.StartInfo.RedirectStandardInput = False
            p.StartInfo.CreateNoWindow = True
            p.StartInfo.Arguments = "/c dir c:\ /w"

            '起動
            p.Start()

            '非同期で出力とエラーの読み取りを開始
            p.BeginOutputReadLine()
            p.BeginErrorReadLine()

            p.WaitForExit()
            p.Close()

            Console.ReadLine()
        End Sub

        'OutputDataReceivedイベントハンドラ
        '行が出力されるたびに呼び出される
        Private Sub p_OutputDataReceived(sender As Object, _
                e As System.Diagnostics.DataReceivedEventArgs)

            '出力された文字列を表示する
            TextBox2.Text = e.Data

        End Sub

        'ErrorDataReceivedイベントハンドラ
        Private Shared Sub p_ErrorDataReceived(sender As Object, _
                e As System.Diagnostics.DataReceivedEventArgs)

            'エラー出力された文字列を表示する
            TextBox2.Text = "ERR>{" + e.Data + "}"
        End Sub

        'ErrorDataReceivedイベントハンドラ
        Private Shared Sub p_ErrorDataReceived(sender As Object, _
                e As System.Diagnostics.DataReceivedEventArgs)

            'エラー出力された文字列を表示する
            'TextBox2.Text = "ERR>{" + e.Data + "}"
        End Sub

    TextBox2に文字列を表示したいのですが、方法が分からなく、教えてほしいです。

    お願いいたします

    2020年9月2日 6:36

回答

  • 上記、項目のSub()のところなのですが、エラーがでてどうやるべきなのか理解できなく

    一瞬、VB のバージョンが古くて Sub() ラムダ式が使えないのかと思いましたが、一番最初の質問を見る限り、最低でも Visual Basic 2010 Service Pack 1 以降のバージョンをお使いのようですね。(VB2010 なら Sub ラムダは使えるはず)

    Gekka さんのコードは、Async キーワードを使っていることから、Visual Basic 2012 以降を想定したものとなっていますが、その部分がエラーになったとは特に言及が無いので、VB バージョンが原因でエラーになっているわけでは無さそうです。

    'なんでかSharedになってる?TextBoxもSharedになってないとエラーに

    Shared にしてはいけません。TextBox2 も イベントプロシージャ―も、どちらも Shared を削ってみてください。

    (Shared キーワードの意味は分かりますか?)

    2020年9月7日 5:11
  • プロセスのDataReceivedなどは別スレッドで呼び出しが行われます。
    しかしTextBoxなどのコントロールを操作するにはメインのスレッド内でしか行えません
    いいかえるとTextBoxのTextプロパティをメインスレッドで操作しなければなりません。
    そのためInvokeもしくはBeginInvokeでスレッドを切り替えて処理させます。

    ただしInvoke,BeginInvokeを使用した場合は、提示されているコードではButton4_Clickを抜けたあとに、その処理が実行されます。
    しかし、Button4_Click内の処理がメインスレッドで処理されているWaitForExitで止まったままになるため、いつまでたってもButton4_Clickを抜けません。
    わるいことに、Invokeを使った場合はButton4_Clickが終わるまで待機し続けるため、いつまでたってもTextBox.Textプロパティを変更する処理が呼び出されなくなり、お互いに待機し続けるようになってしまい、デッドロックします。

    Public Class Form1
        'TextBox2.Invokeを使った場合にデッドロックするのでAsyncに
        Private Async Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
    
    
            'Processオブジェクトを作成
            Dim p As New System.Diagnostics.Process()
    
            '出力とエラーをストリームに書き込むようにする
            p.StartInfo.UseShellExecute = False
            p.StartInfo.RedirectStandardOutput = True
            p.StartInfo.RedirectStandardError = True
            'OutputDataReceivedとErrorDataReceivedイベントハンドラを追加
            AddHandler p.OutputDataReceived, AddressOf p_OutputDataReceived
            AddHandler p.ErrorDataReceived, AddressOf p_ErrorDataReceived
    
            p.StartInfo.FileName = System.Environment.GetEnvironmentVariable("ComSpec")
            p.StartInfo.RedirectStandardInput = False
            p.StartInfo.CreateNoWindow = True
            p.StartInfo.Arguments = "/c dir c:\ /w"
    
            '起動
            p.Start()
    
            '非同期で出力とエラーの読み取りを開始
            p.BeginOutputReadLine()
            p.BeginErrorReadLine()
    
            'p.WaitForExit()'WaitForExitをメインスレッドで使うとデッドロックが発生しやすくなる
    
            'WaitForExitを使わずに、Exitイベントを待つように変更
            p.EnableRaisingEvents = True 'Exitedイベントが発火するように
            Dim tcs As New System.Threading.Tasks.TaskCompletionSource(Of Boolean)
            AddHandler p.Exited, Sub(sender2 As Object, e2 As EventArgs)
                                     tcs.TrySetResult(True) 'Exitedイベントが発生したら待機を抜けるように
                                 End Sub
            Await tcs.Task 'Exitedイベントの発生を待つ
    
            p.Close()
            Console.ReadLine() 'なんでかコンソール読み込み?
        End Sub
    
    
        'OutputDataReceivedイベントハンドラ
        '行が出力されるたびに呼び出される
        Private Sub p_OutputDataReceived(sender As Object, e As System.Diagnostics.DataReceivedEventArgs)
    
            '出力された文字列を表示する
            TextBox2.BeginInvoke(Sub() 'BeginInvokeを使ってメインスレッドで処理をさせる
                                     If e.Data IsNot Nothing Then
                                         TextBox2.AppendText(e.Data)
                                     End If
                                 End Sub)
        End Sub
    
        'ErrorDataReceivedイベントハンドラ
        Private Shared Sub p_ErrorDataReceived(sender As Object, e As System.Diagnostics.DataReceivedEventArgs) 'なんでかSharedになってる?TextBoxもSharedになってないとエラーに
    
            'エラー出力された文字列を表示する
            TextBox2.BeginInvoke(Sub() 'BeginInvokeを使ってメインスレッドで処理をさせる
                                     TextBox2.AppendText("ERR>{" + e.Data + "}")
                                 End Sub)
        End Sub
    End Class
    


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2020年9月2日 13:04
  • いろいろとサポート頂きまして、ありがとうございます。

        'OutputDataReceivedイベントハンドラ
        '行が出力されるたびに呼び出される
        Private Sub p_OutputDataReceived(sender As Object, e As System.Diagnostics.DataReceivedEventArgs)
    
            '出力された文字列を表示する
            TextBox2.BeginInvoke(Sub() 'BeginInvokeを使ってメインスレッドで処理をさせる
                                     If e.Data IsNot Nothing Then
                                         TextBox2.AppendText(e.Data)
                                     End If
                                 End Sub)
        End Sub
    
        'ErrorDataReceivedイベントハンドラ
        Private Shared Sub p_ErrorDataReceived(sender As Object, e As System.Diagnostics.DataReceivedEventArgs) 'なんでかSharedになってる?TextBoxもSharedになってないとエラーに
    
            'エラー出力された文字列を表示する
            TextBox2.BeginInvoke(Sub() 'BeginInvokeを使ってメインスレッドで処理をさせる
                                     TextBox2.AppendText("ERR>{" + e.Data + "}")
                                 End Sub)
        End Sub

    上記、項目のSub()のところなのですが、エラーがでてどうやるべきなのか理解できなく

    すみません。わからないです。

    2020年9月7日 4:00

すべての返信

  • プロセスのDataReceivedなどは別スレッドで呼び出しが行われます。
    しかしTextBoxなどのコントロールを操作するにはメインのスレッド内でしか行えません
    いいかえるとTextBoxのTextプロパティをメインスレッドで操作しなければなりません。
    そのためInvokeもしくはBeginInvokeでスレッドを切り替えて処理させます。

    ただしInvoke,BeginInvokeを使用した場合は、提示されているコードではButton4_Clickを抜けたあとに、その処理が実行されます。
    しかし、Button4_Click内の処理がメインスレッドで処理されているWaitForExitで止まったままになるため、いつまでたってもButton4_Clickを抜けません。
    わるいことに、Invokeを使った場合はButton4_Clickが終わるまで待機し続けるため、いつまでたってもTextBox.Textプロパティを変更する処理が呼び出されなくなり、お互いに待機し続けるようになってしまい、デッドロックします。

    Public Class Form1
        'TextBox2.Invokeを使った場合にデッドロックするのでAsyncに
        Private Async Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
    
    
            'Processオブジェクトを作成
            Dim p As New System.Diagnostics.Process()
    
            '出力とエラーをストリームに書き込むようにする
            p.StartInfo.UseShellExecute = False
            p.StartInfo.RedirectStandardOutput = True
            p.StartInfo.RedirectStandardError = True
            'OutputDataReceivedとErrorDataReceivedイベントハンドラを追加
            AddHandler p.OutputDataReceived, AddressOf p_OutputDataReceived
            AddHandler p.ErrorDataReceived, AddressOf p_ErrorDataReceived
    
            p.StartInfo.FileName = System.Environment.GetEnvironmentVariable("ComSpec")
            p.StartInfo.RedirectStandardInput = False
            p.StartInfo.CreateNoWindow = True
            p.StartInfo.Arguments = "/c dir c:\ /w"
    
            '起動
            p.Start()
    
            '非同期で出力とエラーの読み取りを開始
            p.BeginOutputReadLine()
            p.BeginErrorReadLine()
    
            'p.WaitForExit()'WaitForExitをメインスレッドで使うとデッドロックが発生しやすくなる
    
            'WaitForExitを使わずに、Exitイベントを待つように変更
            p.EnableRaisingEvents = True 'Exitedイベントが発火するように
            Dim tcs As New System.Threading.Tasks.TaskCompletionSource(Of Boolean)
            AddHandler p.Exited, Sub(sender2 As Object, e2 As EventArgs)
                                     tcs.TrySetResult(True) 'Exitedイベントが発生したら待機を抜けるように
                                 End Sub
            Await tcs.Task 'Exitedイベントの発生を待つ
    
            p.Close()
            Console.ReadLine() 'なんでかコンソール読み込み?
        End Sub
    
    
        'OutputDataReceivedイベントハンドラ
        '行が出力されるたびに呼び出される
        Private Sub p_OutputDataReceived(sender As Object, e As System.Diagnostics.DataReceivedEventArgs)
    
            '出力された文字列を表示する
            TextBox2.BeginInvoke(Sub() 'BeginInvokeを使ってメインスレッドで処理をさせる
                                     If e.Data IsNot Nothing Then
                                         TextBox2.AppendText(e.Data)
                                     End If
                                 End Sub)
        End Sub
    
        'ErrorDataReceivedイベントハンドラ
        Private Shared Sub p_ErrorDataReceived(sender As Object, e As System.Diagnostics.DataReceivedEventArgs) 'なんでかSharedになってる?TextBoxもSharedになってないとエラーに
    
            'エラー出力された文字列を表示する
            TextBox2.BeginInvoke(Sub() 'BeginInvokeを使ってメインスレッドで処理をさせる
                                     TextBox2.AppendText("ERR>{" + e.Data + "}")
                                 End Sub)
        End Sub
    End Class
    


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    2020年9月2日 13:04
  • いろいろとサポート頂きまして、ありがとうございます。

        'OutputDataReceivedイベントハンドラ
        '行が出力されるたびに呼び出される
        Private Sub p_OutputDataReceived(sender As Object, e As System.Diagnostics.DataReceivedEventArgs)
    
            '出力された文字列を表示する
            TextBox2.BeginInvoke(Sub() 'BeginInvokeを使ってメインスレッドで処理をさせる
                                     If e.Data IsNot Nothing Then
                                         TextBox2.AppendText(e.Data)
                                     End If
                                 End Sub)
        End Sub
    
        'ErrorDataReceivedイベントハンドラ
        Private Shared Sub p_ErrorDataReceived(sender As Object, e As System.Diagnostics.DataReceivedEventArgs) 'なんでかSharedになってる?TextBoxもSharedになってないとエラーに
    
            'エラー出力された文字列を表示する
            TextBox2.BeginInvoke(Sub() 'BeginInvokeを使ってメインスレッドで処理をさせる
                                     TextBox2.AppendText("ERR>{" + e.Data + "}")
                                 End Sub)
        End Sub

    上記、項目のSub()のところなのですが、エラーがでてどうやるべきなのか理解できなく

    すみません。わからないです。

    2020年9月7日 4:00
  • 上記、項目のSub()のところなのですが、エラーがでてどうやるべきなのか理解できなく

    一瞬、VB のバージョンが古くて Sub() ラムダ式が使えないのかと思いましたが、一番最初の質問を見る限り、最低でも Visual Basic 2010 Service Pack 1 以降のバージョンをお使いのようですね。(VB2010 なら Sub ラムダは使えるはず)

    Gekka さんのコードは、Async キーワードを使っていることから、Visual Basic 2012 以降を想定したものとなっていますが、その部分がエラーになったとは特に言及が無いので、VB バージョンが原因でエラーになっているわけでは無さそうです。

    'なんでかSharedになってる?TextBoxもSharedになってないとエラーに

    Shared にしてはいけません。TextBox2 も イベントプロシージャ―も、どちらも Shared を削ってみてください。

    (Shared キーワードの意味は分かりますか?)

    2020年9月7日 5:11