質問者
処理中のボタン押し受付

質問
すべての返信
-
その形を崩さずにおこなう場合は、前スレで出てきたApplication.DoEventsを使います。
_Canceled=False
For Each ファイル in ファイルリスト
DoEvents
If _Canceled Then ※中止ボタンで_Canceled=Trueにします。
Exit For
End If
ファイルの処理
Next
この場合、繰り返しのファイルの処理をおこなう直前で判断する猶予を与えています。
ただし、前スレで出てきたように副作用に気をつけなければなりません。>中止ボタン以外は殺すとか。
本来、中止ボタン等がある場合は文字通り重い処理と思われますので、
BackgroundWorkerコンポーネントやThreadで重い処理を別にして、GUI(メインスレッド)を普通に操作できるようにしておくほうがよいでしょう。
-
バックグラウンド処理を途中でキャンセルするには?[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 -
まどか さんからの引用 その形を崩さずにおこなう場合は、前スレで出てきたApplication.DoEventsを使います。
_Canceled=False
For Each ファイル in ファイルリスト
DoEvents
If _Canceled Then ※中止ボタンで_Canceled=Trueにします。
Exit For
End If
ファイルの処理
Next
この処理の難点は、DoEventsが実行されるとき以外はGUIが反応しないことです。
中止ボタンをクリックしても見た目何の反応も見られませんが、
DoEventsのステップでボタンにフォーカス枠が付いたり凹んだり凸んだりが一気におこなわれます。
それを回避するには、DoEventsを所々へ細かに記述するしかありません。 しかし、その分処理に割り込みが入るわけで当然遅くなります。
-
長い処理中に早くキャンセルできないのが気になりそうなので
今回は勉強を兼ねてスレッドでサンプルを書いてみました。(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.ClickCancel.Enabled = True
BgWorker1.RunWorkerAsync() '時間のかかる処理のスレッド開始
End Sub
'バックグラウンドで実行するスレッド処理
Private Sub BgWorker1_DoWork(ByVal sender As System.Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs) _
Handles BgWorker1.DoWorkDim 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 SubPrivate Sub Cancel_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Cancel.ClickBgWorker1.CancelAsync()
End SubPrivate Sub BgWorker1_Complete(ByVal sender As System.Object, _
ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
Handles BgWorker1.RunWorkerCompletedIf e.Cancelled Then
MessageBox.Show("Cancelled.")
Else
MessageBox.Show("Normal Finish")
End IfCancel.Enabled = False
End Sub
End Class備忘録代わりでもあるのですが。。(汗)
気になるところがありましたら是非ご指摘ください。。
この前、VC++2005で、たかがCancelボタンと思いきや同じ目に遭い、スレッド処理で書き直した
のですが時間のかかる処理を書く場合は基本的にそうした方がよいですよね。VBの方がすっきり
見えますね!
まどかさん、trapemiyaさん毎回ご教授どうもありがとうございます!
-
動作はするのですがDebugモードで実行して見ると次のメッセージがOutput欄に
表示されているのが気になります。これはどういう意味なのでしょうか?
スミマセン、なにかコメントがありましたら宜しくお願いします。m(_ _)m
コード ブロックA first chance exception of type 'System.DLLNotFoundException' occured in System.Windows.Forms.dll
trapemiyaさんご紹介のサンプルをdownloadして実行してみたところ、同じメッセージが
出ているようなので無視してよいのでしょうか。
-
よしろう さんからの引用 コード ブロック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で動かしてんじゃないの? ならば、普通の動きだよ。
だそうです。
-
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.ClickCancel.Enabled = True
BgWorker1.RunWorkerAsync() '時間のかかる処理のスレッド開始
End Sub
'バックグラウンドで実行するスレッド処理
Private Sub BgWorker1_DoWork(ByVal sender As System.Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs) _
Handles BgWorker1.DoWorkDim worker As BackgroundWorker = CType(sender, BackgroundWorker)
Dim I As Integer
For I = 1 To 50' キャンセルされてないかチェック
If worker.CancellationPending Then
e.Cancel = True
Return
End IfTextBox1.Text = I ' 進捗を文字表示
Debug.Print(I)
Threading.Thread.Sleep(100)
Next
End SubPrivate Sub Cancel_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Cancel.ClickBgWorker1.CancelAsync()
End SubPrivate Sub BgWorker1_Complete(ByVal sender As System.Object, _
ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
Handles BgWorker1.RunWorkerCompletedIf e.Cancelled Then
TextBox1.Text = "実行中断" ' 進捗を文字表示
MessageBox.Show("Cancelled.")
Else
TextBox1.Text = "実行完了" ' 進捗を文字表示
MessageBox.Show("Normal Finish")
End IfCancel.Enabled = False
End Sub
End Class -
まどか さんからの引用 その形を崩さずにおこなう場合は、前スレで出てきたApplication.DoEventsを使います。
_Canceled=False
For Each ファイル in ファイルリスト
DoEvents
If _Canceled Then ※中止ボタンで_Canceled=Trueにします。
Exit For
End If
ファイルの処理
Next
この場合、繰り返しのファイルの処理をおこなう直前で判断する猶予を与えています。
ただし、前スレで出てきたように副作用に気をつけなければなりません。>中止ボタン以外は殺すとか。
まどかさん、
急ぎで作っていた方のプログラムは結局スレッドに移し切れずに、Application.DoEventsを
使わせて頂きました。私も繰り返しのファイル処理でファイルが出来なかった場合の中止として
入れました。現実の処理をスレッドにするとエラー処理が難しいです。返ってバグが増えそうで。。(汗)
短期間で作る場合はApplication.DoEventsの方が便利ですね。
-
よしろう さんからの引用 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を使わず簡単に実現できます。
-
れいさん、
こちらの方にもコメントありがとうございます。
今回の本題とは別の話題に入りつつあるのですが、まずは「強制中断」の処理を先に
組込みたいと思います。元のボタンには既に、Idleイベントは前スレから入れてある
ので(2重押し防止)もう1つIdleイベントを追加すればよいですね。ちょっと処理が複雑に
なりつつありますが、シングルスレッドならまだエラー処理しやすいからよいかと思います。
れい さんからの引用 細切れにできる処理なら、処理の経過状態を表す変数を用意して、
TimerやIdleイベントで細切れに処理する手を使うのも良い方法です。
これならシングルスレッドで且つDoEventsを使わず簡単に実現できます。
スレッドからGUI FORMに文字を直接表示させるのはやはり正しくないということですね。
これに関しては上記にtrapemiyaさんの参照ページにいくつか載っていたのを勉強して
みようかと思っています。
-
投稿後、一度修正しました。
BackGroundWorkerを使用する際の「お決まりのコード」をラッピングしてみました。このクラスを使用するとクライアントコードが簡潔になります。もし良ければ使ってください。(VSエディタで表示するとかなり見やすくなります)
※青色の箇所がポイントです
'このクラスはライブラリに入れる
Imports system.ComponentModelPublic 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 SubDim 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 = TrueMe.OnExProgressChanged(e)
'Application.DoEvents() ←不要。無くても動作する。
callInProgress = False
End SubPrivate 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 SubEnd 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 Form2Dim 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 SubPrivate Sub BtnCancel_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles BtnCancel.Click
w.Stop()
End SubPrivate Sub w_ExProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles w.ExProgressChanged
Label1.Text = e.UserState.ToString
End SubPrivate 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.このコードに問題点や改善すべき点があれば教えてください。私も勉強中なので・・・
以上です。