質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.24%

別スレッドからのコントロール

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,667

tamachan

score 18

本スレッドでの処理をbackgroundworkerで別スレッドで実行します。
別スレッドの処理結果を本スレッドのテキストボックスに反映させたいのですがうまくいきません。

まず、backgroundworker1_DoWorkでの処理の結果をBackgroundWorker1_ProgressChangedで受け取れていないみたいです。

どのようにすれば良いでしょうか。
よろしくお願いいたします。

Public Class bean
    Private no As Integer
    Private result As String
    Public Property No As Integer
        Get
            Return no
        End Get
        Set(value As Integer)
            no = value
        End Set
    End Property
    Public Property Result As String
        Get
            Return result
        End Get
        Set(value As String)
            result = value
        End Set
    End Property
End Class

Public Class clsMain
Public Function prepare() As List(Of bean)
        Dim lst As New List(Of bean)
        Dim b As bean = Nothing
        b = New bean
        b.No = 1
        lst.Add(b)
     ・
     ・
        Return lst
    End Function
    Public Sub check(lst As List(Of bean))
        cal()
     ・
     ・
    End Sub
End Class

Public Class frmMain
    Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
        BackgroundWorker1.WorkerSupportsCancellation = True
        BackgroundWorker1.WorkerReportsProgress = True
        BackgroundWorker1.RunWorkerAsync()
    End Sub
    Private Sub backgroundworker1_DoWork(
             ByVal sender As Object,
             ByVal e As System.ComponentModel.DoWorkEventArgs) _
             Handles BackgroundWorker1.DoWork
        Dim lst As List(Of bean)
        While True
            If BackgroundWorker1.CancellationPending Then
                Exit Sub
            End If
            Dim CLSMain As clsMain = New clsMain
            lst = CLSMain.prepare()
            CLSMain.check(lst)
            BackgroundWorker1.ReportProgress(0, lst)
            Threading.Thread.Sleep(60000)
        End While     
e.Result = lst
    End Sub
    Private Sub BackgroundWorker1_ProgressChanged(
        ByVal sender As Object,
        ByVal e As ProgressChangedEventArgs) _
        Handles BackgroundWorker1.ProgressChanged
        Dim lst As List(Of bean) = DirectCast(e.UserState, List(Of bean))
    End Sub
    Private Sub BackgroundWorker1_RunWorkerCompleted(
        ByVal sender As System.Object,
        ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
        Handles BackgroundWorker1.RunWorkerCompleted
        If e.Cancelled = True Then
            'BackgroundWorker1.CancelAsync()
        Else
            Dim lst As List(Of bean) = DirectCast(e.Result, List(Of bean))
        For Each r As bean In lst
            Dim rResult As Control() = Controls.Find("txtBox" & r.No, True)      
            CType(rResult(0), TextBox).Text = r.Result
        Next
        End If
    End Sub
    Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click
    'ストップボタンでBackgroundWorkerを停止させたい
        BackgroundWorker1.CancelAsync()
    End Sub
End Class
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

「受け取れていない」の詳細がわかりませんが、lstに期待している値が入っていないという事でしょうか。
ざっと見ただけですが、気になる点をいくつか。

  • ReportProgresslstを渡していますが、BackgroundWorker1_ProgressChangedイベントで受け取っていません。

以下の様にProgressChangedEventArgsから取り出すようにしてください

Private Sub BackgroundWorker1_ProgressChanged(
        ByVal sender As Object,
        ByVal e As ProgressChangedEventArgs) _
        Handles BackgroundWorker1.ProgressChanged

        Dim lst As List(Of bean) = DirectCast(e.UserState, List(Of bean))

そして、DoWorkのlstはFormのlstを使おうとしていますが、DoWorkで別途用意した方が良いでしょう(Dimを付けて別のlstにした方が良いでしょう)

  • BackgroundWorkerのDoWork内の処理結果は、DoWorkのDoWorkEventArgsのResultプロパティにセットしてください。

通常結果データは、DoWorkのReulstに値をセットして、RunWorkerCompletedイベントで受け取ります。
DoWorkイベントが終了するとRunWorkerCompletedイベントが発行されます。
ただ、今回は終了させないみたい?なので、このやり方は当てはまらないかもしれません。

 DoWorkイベントの最後 

BackgroundWorker1.ReportProgress(0, lst)
            Threading.Thread.Sleep(60000)
        End While
        e.Result = lst
    End Sub

 RunWorkerCompleted  

Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted

        If (e.Cancelled) Then

        Else
          Dim lst As List(Of bean) = DirectCast(e.Result, List(Of bean))
          ' 処理を書く

        End If
    End Sub
  • DoWorkでキャンセル判定をしているが、キャンセル可能でない可能性がある

これはこれからやるのかもしれませんが、BackgroundWorker1.WorkerSupportsCancellationにTrueをセットしていないので、キャンセル処理は受け付けられません(その場合エラーになる気がする)
※ デザイナでセットしているなら無視してください。


毎回1分待機させて終了させないBackgroundWorkerなら最初の指摘を直せば動きそうな気がします。
ただそういう場合、1分おきに実行はTimerに任せて、BackgroundWorkerは毎回終了するようにした方が良いように思います。


追記:8/14 15:14

提示のコードで試してみました。処理としては、e.Resultにlstをセットして、RunWorkerCompletedで受け取っているので、そこは問題ない様に見えます。
後述する「DoWorkで使っているlst変数が宣言されていない」(frmMainのものを使ってしまっていて状態が不明瞭に)という問題がありますが、最終的にはclsMainのprepareで何件のリストを返しているかによるので、そこの実際の処理を見直してみるしかなさそうです。

txtBox?の数字がbeanのNoときちんとあっているかは試してません。
Controls.Findとその次の行をコメントアウトして動くなら、受け渡しの問題ではありませんね。

For Each r As bean In lst
    'Dim rResult As Control() = Controls.Find("txtBox" & r.No, True)
    'CType(rResult(0), TextBox).Text = r.Result
Next

長くてすいませんが、気になる点を指摘してみます。

まずは軽微なところから

  • Cancel処理で正常終了
    ここは今のところの工夫ということで後回しで良いですが、CancelAsyncを読んだらキャンセルされて欲しいので、中断/終了は別の方法をとった方が良いでしょう。これは後で書きます。
  • beanのプロパティと内部変数名が同じ名前
    VB.NETは大文字と小文字を区別しないので、privateのnoとPropertyのNoが二重定義でエラーになります。
    これは質問用に書き換えたときのミスだろうと思うので。
  • 変数CLSMainとCLSListMain
    質問に記載するときのミスなら問題ありませんが、そうでないなら問題です。
' DoWork内
   Dim CLSMain As clsMain = New clsMain  ’CLSMain 変数を用意
   lst = CLSListMain.prepare()           'CLSListMainとは?
  • RunWorkerCompleted内でのCancelAsync
     If e.Cancelled = True Thenの次の行
    これは単純に不要です。ここに入ったときはすでにキャンセルされているので。

その他の問題となりそうなところ

  • Threading.Thread.Sleep(60000)
    これだとWhile無いが1回実行されるたびに 1分待つことになります。
    現在の問題と関係ないので…と思ってましたが実際に書いてみたら理由がわかった気がします。
    これがないと非同期なはずなのに固まるから入れたんではないでしょうか。
    もしそうなら固まる理由はReportProgressを呼びすぎていることにあります。
    ReportProgressを実行する間は画面側の処理になりますので、これを過剰に呼ぶということは、裏方(別スレッド)に任せたのに、状態を確認しにいって何もできてない状態です。
    ウェイトを入れるか、ReportProgressを呼ぶ条件をつけたほうがいいでしょう(カウンタを用意しておいて100回に1回だけ呼ぶなど)。
    今は、Thread.Sleep(300)ぐらいにしておけばいいと思います。実際のClsMainがなにしているのかにもよりますが。ここでSleepしている=作業用スレッドは止まっているということに注意してください。

  • DoWorkで使っているlst変数が宣言されていないのと List(Of bean) をnewしすぎ。
    結論から言えば、lst( List(Of bean) )のNewはclsMainのprepare()でnewしているので他は必要ありません。
    DoWork内では変数を用意すればいいだけです。
    今の作りだとWhile Trueのすぐ前の行に用意すればいいかと。

Dim lst As List(Of bean)
   While True


そして、frmMainの先頭の private lst ~ の行は不要になりますので削除します。
スレッド内(DoWork内)では、スレッド外部で宣言されている変数はあまり触らないようにします(Threadとの連携のために用意したものならいいですが、DoWorkでもfrmMainでも同時に変更したとき厄介だからです)。

  • clsMainでのList(Of bean)の初期化はただしいか。
    先ほど、clsMain内だけ List(Of bean) のnewを残しました。
    実際の処理次第ではありますが、prepareでListを毎回newしてしまうのでなにか外部のデータでもない限り、その都度Listは空になります。prepare内でアイテムを追加してますので、その回で追加されたアイテム分しか結果としてのこりません。
    そこでデータが変わらないならWhileでループしても何回やっても同じになってしまうなと感じました。時刻によってデータが変わったり外部との連携があるなら問題ないのです。
    ただ、そうであればそのWhileループの回で、追加するものがなにもなければ結果は 0件になります。

  • キャンセルについて
    これは当面の問題が解決してから気にしてください。
    中止という意味でキャンセルさせたいとき、DoWork内で e.Cancelと Trueにセットすると、
    RunWorkerCompleted内で、CancellationPendingがTrueになります。
    キャンセル処理としてはこちらの方が素直な作り方になります。
    終了の条件を別途設ける(frmMainのprivate変数にboolean変数を用意しておくとか、RunWorkerAsyncに連携用オブジェクトを渡しておいて、DoWorkEventArgsから受け取る(結果をe.Resultから受け取るように)とか)必要がありますので、今の問題が片付いてから検討してもいいでしょう。
    単に終了させるのは、e.CancelにFalseをセットした状態でWhile文を抜ければいいだけです。
    キャンセルを終了に流用してるだけなので、そこまで悪いことではないですが、「CancelAsyncよんだけどキャンセルロジックに入らない」というのは後で見るとわかりにくいかもしれません。コメントだけでも残しておくといいでしょう。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/08/14 14:17

    編集しました。
    ・・・まだ処理ができていません。

    はじめ、ProgressChangedでUIのテキストボックスに結果を吐き出す処理を書いていましたが、それをProgressChangedから削除して、RunWorkerCompletedに移し替えてみました。

    DoWorkでlstに処理して出てきた値を格納して、ProgressChangedでlstに格納されていた値をUIのテキストボックスに吐き出すということを意図していたのですが、デバックで確認するとlstの中に格納した値の受け渡しができてないみたいです。
    lstの中身が空になっていました。

    timerは存在を知らなかったので、lstの値の受け渡しが解決しましたら実装しようと思います。

    キャンセル

  • 2016/08/14 15:12

    試してみました。結論から言えば 空ではなかったです(1件入っていました)。
    何カ所か気になる点もあったので、回答に追記します。

    キャンセル

  • 2016/08/14 22:22

    とてもご丁寧な回答ありがとうございます。
    backgroundworkerについて仕組みが徐々に理解してきました。
    しかしまだ値が取れません(泣)

    >これは質問用に書き換えたときのミスだろうと思うので。
    →はい、質問用に書き換えています。その他の部分も質問用に書き換えています。

    無限ループにしているのは目的がサーバー監視のためです。
    clsMainの処理中身はその内容になっています。

    ちなみにbackgroundworkerを実装していないときはちゃんと値も取れテキストボックスにも記載できていました。

    キャンセル

  • 2016/08/15 22:39

    解決しました。
    基本的にflied_onionさんの教えていただいたことで正しいかったです。
    私の質問時に足りない部分があったためでした。

    あらかじめtextBox1に入力している値を読み込んで処理を行い、その結果をtextBox2に反映させるということをお伝えしていなかったことが解決できなかった原因でした。

    lst = CLSMain.prepare()をbackgroundworkerを起動する前に持って来ることで値が取れるようになりました。

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

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.24%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る