none
処理中のボタン押し受付 RRS feed

  • 質問

  • VisualBasic2005でWindows Form Applicationで使用しています。

    Form画面に2つボタンを作り、1つのボタンが押され、ある長い処理を実行中に

    もう1つのボタンを押すことで「強制中断」を行えるようにしたいのですが、実際に

    やってみると、1つのボタンを押した処理中は、もう2つのボタン押し処理を受け

    つけずキューに入るだけです。これを処理を受け付けるようになにかできないで

    しょうか?

    宜しくお願いします。

    2007年11月19日 6:58

すべての返信

  • その形を崩さずにおこなう場合は、前スレで出てきたApplication.DoEventsを使います。

    _Canceled=False

    For Each ファイル in ファイルリスト

        DoEvents

        If _Canceled Then ※中止ボタンで_Canceled=Trueにします。

            Exit For

        End If

        ファイルの処理

    Next

    この場合、繰り返しのファイルの処理をおこなう直前で判断する猶予を与えています。

    ただし、前スレで出てきたように副作用に気をつけなければなりません。>中止ボタン以外は殺すとか。

     

    本来、中止ボタン等がある場合は文字通り重い処理と思われますので、

    BackgroundWorkerコンポーネントやThreadで重い処理を別にして、GUI(メインスレッド)を普通に操作できるようにしておくほうがよいでしょう。

    2007年11月19日 7:30
  • バックグラウンド処理を途中でキャンセルするには?[2.0のみ、C#、VB]
    http://www.atmarkit.co.jp/fdotnet/dotnettips/437bgwcancel/bgwcancel.html

     

    スレッドの参考として、以下の記事を読まれると勉強になると思います。

     

    .NETマルチスレッドプログラミング 1:スレッドの実行と同期
    [初級~中級] .NET Frameworkにおけるマルチスレッドプログラミングの基本
    http://codezine.jp/a/article/aid/135.aspx

    2007年11月19日 7:41
    モデレータ
  • 返信どうもありがとうございます。

    2つの方法があるのですね。まずは試してみます。

    2007年11月19日 10:43
  •  まどか さんからの引用

    その形を崩さずにおこなう場合は、前スレで出てきたApplication.DoEventsを使います。

    _Canceled=False

    For Each ファイル in ファイルリスト

        DoEvents

        If _Canceled Then ※中止ボタンで_Canceled=Trueにします。

            Exit For

        End If

        ファイルの処理

    Next

     

    この処理の難点は、DoEventsが実行されるとき以外はGUIが反応しないことです。

    中止ボタンをクリックしても見た目何の反応も見られませんが、

    DoEventsのステップでボタンにフォーカス枠が付いたり凹んだり凸んだりが一気におこなわれます。

    それを回避するには、DoEventsを所々へ細かに記述するしかありません。 しかし、その分処理に割り込みが入るわけで当然遅くなります。

    2007年11月20日 0:28
  • 長い処理中に早くキャンセルできないのが気になりそうなので

    今回は勉強を兼ねてスレッドでサンプルを書いてみました。(VB2005)

    まどかさん、すみません折角説明頂いたのですが前回の書き込みでDoEventsを使うのにちょっとびびってます。(笑)

    似て非なるものであるならご指摘ください。

    trapemiyaさんご紹介のサンプルを参考にしました。

     

    Form上には2つのボタンがあります。

    ・Button1_Click (実行開始)

    ・Cancel_Click (キャンセルボタン)

     ※CancelボタンのプロパティーでEnabledプロパティーをfalseにしておきます。

    ・ToolBoxのComponentsからBackgroundWorker(BgWorker1にリネーム)を置きます。

     

    BackgroundWorkerのプロパティで次の2つのプロパティーをfalseからtrueにしました。

    ・WorkerSupportsCancellation  false  ----> true

    ・WorkerReportsProgress  false ---> true

     

    Cancel付きスレッド実行

    Imports System.ComponentModel

    Public Class Form1

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

            Cancel.Enabled = True

            BgWorker1.RunWorkerAsync()  '時間のかかる処理のスレッド開始
        End Sub
        'バックグラウンドで実行するスレッド処理
        Private Sub BgWorker1_DoWork(ByVal sender As System.Object, _
            ByVal e As System.ComponentModel.DoWorkEventArgs) _
            Handles BgWorker1.DoWork

            Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
            Dim I As Integer
            For I = 1 To 50
                ' キャンセルされてないかチェック
                If worker.CancellationPending Then
                    e.Cancel = True
                    Return
                End If

                Debug.Print(I)
                Threading.Thread.Sleep(100)
            Next
        End Sub

     

        Private Sub Cancel_Click(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles Cancel.Click

            BgWorker1.CancelAsync()
        End Sub

     

        Private Sub BgWorker1_Complete(ByVal sender As System.Object, _
            ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
            Handles BgWorker1.RunWorkerCompleted

            If e.Cancelled Then
                MessageBox.Show("Cancelled.")
            Else
                MessageBox.Show("Normal Finish")
            End If

            Cancel.Enabled = False
        End Sub
    End Class

     

     

    備忘録代わりでもあるのですが。。(汗)

    気になるところがありましたら是非ご指摘ください。。

     

    この前、VC++2005で、たかがCancelボタンと思いきや同じ目に遭い、スレッド処理で書き直した

    のですが時間のかかる処理を書く場合は基本的にそうした方がよいですよね。VBの方がすっきり

    見えますね!

     

    まどかさん、trapemiyaさん毎回ご教授どうもありがとうございます!

    2007年11月20日 2:53
  • 動作はするのですがDebugモードで実行して見ると次のメッセージがOutput欄に

    表示されているのが気になります。これはどういう意味なのでしょうか?

    スミマセン、なにかコメントがありましたら宜しくお願いします。m(_ _)m

     

    コード ブロック

    A first chance exception of type 'System.DLLNotFoundException' occured in System.Windows.Forms.dll

     

     

     

    trapemiyaさんご紹介のサンプルをdownloadして実行してみたところ、同じメッセージが

    出ているようなので無視してよいのでしょうか。

    2007年11月20日 4:19
  •  よしろう さんからの引用
    コード ブロック

    A first chance exception of type 'System.DLLNotFoundException' occured in System.Windows.Forms.dll

     

     

    ↑そのまんまGoogleに食わせてみました。

    http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=716841&SiteID=1

     

    普通に動かしたんじゃなくてデバッガとかで動かしたんではないかい?

    そんでもってWindows2000で動かしてんじゃないの? ならば、普通の動きだよ。

    だそうです。

    2007年11月20日 6:34
  •  

    まどかさん、

    その通りでした、説明足りなくてスミマセン。。Windows2000上でVS2005のデバッグで動かしてます。

    なるほど、エラーメッセージからも調べられるんですね。色々ありがとうございます。

    2007年11月20日 6:43
  • 1つ質問なのですが、下記のサンプルで動作はしたのですが進捗を文字で表示したい

    場合は下記のTextBoxへの表示方法は問題ないでしょうか?スレッド中からForm上に数字

    を介して(ProgressBarなど)UPDATEする例は見たのですが、文字列の更新がよくわかり

    ませんでした。とりあえず直接代入した例が下記になりますが。(赤文字部)

    ※TextBox1がForm上のテキストBOX

     

    コード ブロック

    Imports System.ComponentModel

    Public Class Form1

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

            Cancel.Enabled = True

            BgWorker1.RunWorkerAsync()  '時間のかかる処理のスレッド開始
        End Sub
        'バックグラウンドで実行するスレッド処理
        Private Sub BgWorker1_DoWork(ByVal sender As System.Object, _
            ByVal e As System.ComponentModel.DoWorkEventArgs) _
            Handles BgWorker1.DoWork

            Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
            Dim I As Integer
            For I = 1 To 50

                ' キャンセルされてないかチェック
                If worker.CancellationPending Then
                    e.Cancel = True
                    Return
                End If

                TextBox1.Text = I ' 進捗を文字表示

                Debug.Print(I)
                Threading.Thread.Sleep(100)
            Next
        End Sub

     

        Private Sub Cancel_Click(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles Cancel.Click

            BgWorker1.CancelAsync()
        End Sub

     

        Private Sub BgWorker1_Complete(ByVal sender As System.Object, _
            ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
            Handles BgWorker1.RunWorkerCompleted

            If e.Cancelled Then

                TextBox1.Text = "実行中断"  ' 進捗を文字表示

                MessageBox.Show("Cancelled.")

            Else

                TextBox1.Text = "実行完了"  ' 進捗を文字表示
                MessageBox.Show("Normal Finish")
            End If

            Cancel.Enabled = False
        End Sub
    End Class

     

     

    2007年11月20日 9:38
  •  まどか さんからの引用

    その形を崩さずにおこなう場合は、前スレで出てきたApplication.DoEventsを使います。

    _Canceled=False

    For Each ファイル in ファイルリスト

        DoEvents

        If _Canceled Then ※中止ボタンで_Canceled=Trueにします。

            Exit For

        End If

        ファイルの処理

    Next

    この場合、繰り返しのファイルの処理をおこなう直前で判断する猶予を与えています。

    ただし、前スレで出てきたように副作用に気をつけなければなりません。>中止ボタン以外は殺すとか。

     

    まどかさん、

    急ぎで作っていた方のプログラムは結局スレッドに移し切れずに、Application.DoEventsを

    使わせて頂きました。私も繰り返しのファイル処理でファイルが出来なかった場合の中止として

    入れました。現実の処理をスレッドにするとエラー処理が難しいです。返ってバグが増えそうで。。(汗)

    短期間で作る場合はApplication.DoEventsの方が便利ですね。

    2007年11月20日 14:41
  •  よしろう さんからの引用

    1つ質問なのですが、下記のサンプルで動作はしたのですが進捗を文字で表示したい

    場合は下記のTextBoxへの表示方法は問題ないでしょうか?

     

    たぶん気づいていると思いますが、これではだめですね。

    別スレッドからコントロールにアクセスしてます。

     

    これにはいくつか方法があります。

    Invokeを使うのが一番楽です。

    Form.InvokeRequireがtrueのときはForm.InvokeかForm.BeginInvokeし、

    Form作成スレッドに処理を移行します。

    (これはForm終了時にハングする危険性があります。)

     

    ProgressChangedEventArgsのuserstateオブジェクトを使うのは少し綺麗です。

    ReportProgress(xxx, userState)を使うとでBackgroundWorkerがそれを作成したスレッドでイベントを発生させます。

    userStateには何でも入りますので、

    適当なものをいれて処理を分岐したりすれば、

    いくらでもどうとでもできます。

     

    もっとも綺麗ですが一番手間のかかるのは、

    BackgroundWorker相当のクラスを自分で実装し、

    AsyncOperation.Postを用いる方法です。

    これは自分で独自のイベントを定義できますので、

    独自のコンポーネントを作るときにいい方法です。

     

    > 短期間で作る場合はApplication.DoEventsの方が便利ですね。

     

    DoEvents反対派としては、圧力をかけないといけません。

    「急がば回れ」ですよ!

    あとでどうなってもしりませんよ!

    原因不明のバグとかに悩まされますよ!

     

    実際には簡単なものならDoEventsでも問題が殆どでないようにするのも可能なんですがね。

    (検証できないけど…)

    一度やりかたを覚えてしまえば、DoEventsを使わないで2度押し防止とか、簡単にできるので、

    危険だときちんと認識し、

    時間の許す限り使わない方向で作ればいいのではないかと。

     

    存在しなくても問題ないメソッドで、必ず代替手法がある、というようなことを以前言いました。

    今回はDoEventsの為に処理を細切れにしてますよね?

    細切れにできる処理なら、処理の経過状態を表す変数を用意して、

    TimerやIdleイベントで細切れに処理する手を使うのも良い方法です。

    これならシングルスレッドで且つDoEventsを使わず簡単に実現できます。

    2007年11月21日 0:16
  • れいさん、

    こちらの方にもコメントありがとうございます。

    今回の本題とは別の話題に入りつつあるのですが、まずは「強制中断」の処理を先に

    組込みたいと思います。元のボタンには既に、Idleイベントは前スレから入れてある

    ので(2重押し防止)もう1つIdleイベントを追加すればよいですね。ちょっと処理が複雑に

    なりつつありますが、シングルスレッドならまだエラー処理しやすいからよいかと思います。

     

     れい さんからの引用

    細切れにできる処理なら、処理の経過状態を表す変数を用意して、

    TimerやIdleイベントで細切れに処理する手を使うのも良い方法です。

    これならシングルスレッドで且つDoEventsを使わず簡単に実現できます。

     

    スレッドからGUI FORMに文字を直接表示させるのはやはり正しくないということですね。

    これに関しては上記にtrapemiyaさんの参照ページにいくつか載っていたのを勉強して

    みようかと思っています。

    2007年11月21日 4:11
  • 投稿後、一度修正しました。

     

    BackGroundWorkerを使用する際の「お決まりのコード」をラッピングしてみました。このクラスを使用するとクライアントコードが簡潔になります。もし良ければ使ってください。(VSエディタで表示するとかなり見やすくなります)

    ※青色の箇所がポイントです


    'このクラスはライブラリに入れる
    Imports system.ComponentModel

     

    Public MustInherit Class CustomBackGroundWorkerBase

     

        ''' <summary>
        ''' 中断可能にするには、メソッド内でIsCancelledプロパティをチェックしてください
        ''' </summary>
        Protected MustOverride Function ExDoWork(ByVal argument As Object) As Object

     

        ' ラッピングされたBackGroundWorker
        Private WithEvents bgw As New BackgroundWorker
        ' イベントの定義

        Public Event ExProgressChanged As ProgressChangedEventHandler
        Public Event ExRunWorkerCompleted As RunWorkerCompletedEventHandler
        Protected Overridable Sub OnExProgressChanged(ByVal e As ProgressChangedEventArgs)
            RaiseEvent ExProgressChanged(Me, e)
        End Sub
        Protected Overridable Sub OnExRunWorkerCompleted(ByVal e As RunWorkerCompletedEventArgs)
            RaiseEvent ExRunWorkerCompleted(Me, e)
        End Sub

        ' プロパティの定義

        ''' <summary>
        ''' 非同期実行中止の指示が出ていればTrue
        ''' </summary>
        Public ReadOnly Property IsCancelled() As Boolean
            Get
                If bgw.CancellationPending = True Then
                    m_cancel = True
                End If
                Return m_cancel
            End Get
        End Property
        Private m_cancel As Boolean = False
        ''' <summary>
        ''' 非同期実行中ならTrue
        ''' </summary>
        Public ReadOnly Property IsInWork() As Boolean
            Get
                Return m_isinwork
            End Get
        End Property
        Private m_isinwork As Boolean = False

        ' コンストラクタ

        Public Sub New()
            bgw.WorkerSupportsCancellation = True
            bgw.WorkerReportsProgress = True
        End Sub

        ' メソッドの定義

        ''' <summary>
        ''' 非同期実行を開始します
        ''' </summary>
        ''' <param name="argument">ExDoWork()に渡す引数</param>
      
      Public Sub Start(ByVal argument As Object)
            m_isinwork = True
            bgw.RunWorkerAsync(argument)
        End Sub
        ''' <summary>
        ''' 非同期実行中止を指示します
        ''' </summary>
        Public Sub [Stop]()
            bgw.CancelAsync()
        End Sub
        ''' <summary>
        ''' 非同期実行の途中経過表示用。OnExProgressChangedイベントを発行します。イベントハンドラは元のスレッド(通常はFormを作成したスレッド)で実行されます。
        ''' </summary>
        Protected Sub ExReportProgress(ByVal percentage As Integer, ByVal state As Object)
            bgw.ReportProgress(percentage, state)
        End Sub

     

        ' BackGroundWorkerのイベントハンドラ

        Private Sub bgw_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgw.DoWork
            Dim o As Object = Me.ExDoWork(e.Argument)
            e.Result = o
        End Sub

     

        Dim callInProgress As Boolean = False
        Private Sub bgw_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles bgw.ProgressChanged
            'ネストした呼び出しは無視
            If callInProgress = True Then Return
            callInProgress = True

     

            Me.OnExProgressChanged(e)

     

            'Application.DoEvents() ←不要。無くても動作する。
            callInProgress = False
        End Sub

     

        Private Sub bgw_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles bgw.RunWorkerCompleted
            Me.OnExRunWorkerCompleted(e)
            m_cancel = False
            m_isinwork = False
        End Sub

    End Class

    ' ここまでライブラリ

     

    ※以下のコードは各アプリケーション毎に記述

     

    ' 派生クラスとクライアントコードの一例です。
    ' (1から10までカウントアップ、キャンセル可能)

     

    ' 派生クラス
    Public Class Worker
        Inherits CustomBackGroundWorkerBase

        ' ここに非同期処理コードを書く
        Protected Overrides Function ExDoWork(ByVal argument As Object) As Object
            Dim i As Integer = CInt(argument)
            Do
                Threading.Thread.Sleep(1000)
                If Me.IsCancelled = True Then Return i
                i += 1
                Me.ExReportProgress(0, i)
            Loop While i < 10
            Return i
        End Function
    End Class

     

    ’フォーム
    Public Class Form2

     

        Dim WithEvents w As New Worker

        Private Sub BtnRun_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles BtnRun.Click
            BtnRun.Enabled = False
            BtnCancel.Enabled = True
            w.Start(0)
        End Sub

     

        Private Sub BtnCancel_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles BtnCancel.Click
            w.Stop()
        End Sub

     

        Private Sub w_ExProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles w.ExProgressChanged
            Label1.Text = e.UserState.ToString
        End Sub

     

        Private Sub w_ExRunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles w.ExRunWorkerCompleted
            Dim ss As String = ""
            'e.Cancelledが使えるようにはできませんでした・・・
            If w.IsCancelled = True Then ss += "キャンセル - " Else ss += "完了 - "
            Label1.Text = ss + e.Result.ToString

            BtnCancel.Enabled = False
            BtnRun.Enabled = True
        End Sub
    End Class

     


    利点
    1.非同期処理コードをWorker内に書くため、コードの分離が良くなる。(この例では、Form内のコードは全てFormを作成したスレッド上で実行されます)
    2.クライアントコードが簡潔になる。

     

    P.S.このコードに問題点や改善すべき点があれば教えてください。私も勉強中なので・・・

     

    以上です。

    2007年11月26日 15:19