- kanai
- ベテラン
- 会議室デビュー日: 2004/09/13
- 投稿数: 98
|
投稿日時: 2005-10-13 13:17
いつもお世話になっております。kanaiです。
時間のかかる処理をクラスの別スレッド上で実行し、その途中経過を
表示するWindowsアプリケーションを作成しております。
別スレッドを実行するクラスはフォームとは別で、途中経過はイベントを
通じて受け取るものとします。
このような場合、フォーム側でInvokeを使用してコントロールの操作が
メインスレッド上で行われるようにする必要がある、と理解していますが、
クラス側でInvoke相当の処理を行ってメインスレッドにマーシャリングすれば、
フォーム側は普通にイベントを処理するだけでよいのでは?と考えました。
もし可能ならば、クラスの利用者(フォーム側)はクラスが内部でスレッドを
使用しているかを意識する必要がなくなり、フォーム側の実装が容易になる
と思った次第です。
が、どのようにすれば実現できるのかわかりません。
コールバックデリゲート(参照元記事)を利用すれば可能かと思い試しましたが、
期待する結果にはなりませんでした。
そもそもこのようなことは可能なのでしょうか?
環境はWindows XP SP2 + VS.NET 2003(VB.NET)です。
サンプルコードは下記の通りです。
コード: |
|
'Form1.vb
'デザイナでButton1とListBox1を貼り付けてください
Public Class Form1
Inherits System.Windows.Forms.Form
#Region " Windows フォーム デザイナで生成されたコード "
'省略
#End Region
Private WithEvents c1 As New Class1
Private Sub Button1_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs _
) Handles Button1.Click
c1.Start()
End Sub
Private Sub c1_Progress( _
ByVal sender As Object, _
ByVal e As Class1.ProgressEventArgs _
) Handles c1.Progress
Me.Invoke( _
New AddListItemDelegate(AddressOf AddListItem), _
New Object() {e.Message})
'できれば、次のようにフォーム側ではInvokeをしなくてもよいようにしたい
'AddListItem(e.Message)
End Sub
Private Delegate Sub AddListItemDelegate(ByVal message As String)
Private Sub AddListItem(ByVal message As String)
ListBox1.Items.Add(Threading.Thread.CurrentThread.Name & ":" & message)
End Sub
End Class
'Class1.vb
Public Class Class1
'スレッドオブジェクト
Private _SubThread As Threading.Thread
'別スレッドの処理を開始する
Public Sub Start()
'メインスレッドに名前を付ける
Threading.Thread.CurrentThread.Name = "メインスレッド"
'別スレッドを開始する
_SubThread = New Threading.Thread( _
New Threading.ThreadStart(AddressOf SubThreadProcess))
_SubThread.Name = "サブスレッド"
_SubThread.Start()
End Sub
'別スレッドで実行する処理
Private Sub SubThreadProcess()
'処理1
Threading.Thread.Sleep(1000)
OnProgress("処理1が終了しました。") 'この処理をメインスレッドで実行したい
'処理2
Threading.Thread.Sleep(1000)
OnProgress("処理2が終了しました。") 'この処理をメインスレッドで実行したい
'処理3
Threading.Thread.Sleep(1000)
OnProgress("処理3が終了しました。") 'この処理をメインスレッドで実行したい
End Sub
'処理の途中経過を報告するイベント
Public Event Progress(ByVal sender As Object, ByVal e As ProgressEventArgs)
'Progressイベントを発生させる
Private Sub OnProgress(ByVal message As String)
RaiseEvent Progress(Me, New ProgressEventArgs(message))
End Sub
'Progressイベントのイベント変数クラス
Public Class ProgressEventArgs
Inherits EventArgs
Private _message As String
Public ReadOnly Property Message() As String
Get
Return _message
End Get
End Property
Public Sub New(ByVal message As String)
_message = message
End Sub
End Class
End Class
|
|
- なちゃ
- ぬし
- 会議室デビュー日: 2003/06/11
- 投稿数: 872
|
投稿日時: 2005-10-13 13:49
引用: |
|
kanaiさんの書き込み (2005-10-13 13:17) より:
このような場合、フォーム側でInvokeを使用してコントロールの操作が
メインスレッド上で行われるようにする必要がある、と理解していますが、
クラス側でInvoke相当の処理を行ってメインスレッドにマーシャリングすれば、
フォーム側は普通にイベントを処理するだけでよいのでは?と考えました。
もし可能ならば、クラスの利用者(フォーム側)はクラスが内部でスレッドを
使用しているかを意識する必要がなくなり、フォーム側の実装が容易になる
と思った次第です。
|
TimerやProcess等、イベントを持つコンポーネントを見てみるとヒントが見えてくるかもしれません。
※このようなときに、.NET Frameworkにおいて標準的と思われる構造に関して
SynchronizingObjectとかISynchronizeInvokeとかの説明を見るといいです。
必ずしもこれに合わせる必要はないですが。
|
- まどか
- ぬし
- 会議室デビュー日: 2005/09/06
- 投稿数: 372
- お住まい・勤務地: ますのすし管区
|
投稿日時: 2005-10-13 14:14
引用: |
|
コード: |
|
Public Class Class1
'別スレッドで実行する処理
Private Sub SubThreadProcess()
'処理1
Threading.Thread.Sleep(1000)
OnProgress("処理1が終了しました。") 'この処理をメインスレッドで実行したい
End Sub
End Class
|
|
呼び出し側が処理方法を意識しない、つまりClass1が公開クラスという位置づけだと思います。
単にそれだけの問題なら、Class1に通知するインターフェースを実装すればよいのではないでしょうか。
Invokeうんぬんに関しては、別スレッドが直接呼ぶのかClass1が仲介して呼ぶのかに関わらず
呼び出し側がデリゲート参照を渡してあげればよいと思います。
|
- kanai
- ベテラン
- 会議室デビュー日: 2004/09/13
- 投稿数: 98
|
投稿日時: 2005-10-13 18:12
なちゃさん、まどかさんご回答いただきありがとうございます。
.NETの作法(?)に従い、クラス側にSynchronizingObject
プロパティを実装することで期待する動作を実現できました。
当初の目的としては、フォーム側から情報を渡さずに、クラス自身で
メインスレッドへのマーシャリングを行うことでしたが、この方法が
標準的、というのであればそれに従おうと思います。
フォーム側でデリゲートやInvokeを実装しなければならないのと比べて、
SynchronizingObject = Meを設定する程度であれば、(クラスの利用者
にとって)十分簡単、という判断もあります。
ところで、今回はじめて知ったのですが、ProcessやTimerは
デザイナに貼り付けると自動的に"SynchronizingObject = Me"が
設定されるようですね。これはやはり属性とかで定義されている
のでしょうか?
(自動的に設定したい、というわけではないのですが少し気になったので)
修正したサンプルコードは下記の通りです。
コード: |
|
'Form1.vb
'デザイナでButton1とListBox1を貼り付けてください
Public Class Form1
Inherits System.Windows.Forms.Form
#Region " Windows フォーム デザイナで生成されたコード "
'省略
#End Region
Private WithEvents c1 As New Class1
Private Sub Button1_Click( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs _
) Handles Button1.Click
c1.SynchronizingObject = Me
c1.Start()
End Sub
Private Sub c1_Progress( _
ByVal sender As Object, _
ByVal e As Class1.ProgressEventArgs _
) Handles c1.Progress
'Me.Invoke( _
' New AddListItemDelegate(AddressOf AddListItem), _
' New Object() {e.Message})
'できれば、次のようにフォーム側ではInvokeをしなくてもよいようにしたい
AddListItem(e.Message)
End Sub
Private Delegate Sub AddListItemDelegate(ByVal message As String)
Private Sub AddListItem(ByVal message As String)
ListBox1.Items.Add(Threading.Thread.CurrentThread.Name & ":" & message)
End Sub
End Class
'Class1.vb
Public Class Class1
'スレッドオブジェクト
Private _SubThread As Threading.Thread
'マーシャリング用オブジェクト
Private _SynchronizingObject As System.ComponentModel.ISynchronizeInvoke
Public Property SynchronizingObject() As System.ComponentModel.ISynchronizeInvoke
Get
Return _SynchronizingObject
End Get
Set(ByVal Value As System.ComponentModel.ISynchronizeInvoke)
_SynchronizingObject = Value
End Set
End Property
'別スレッドの処理を開始する
Public Sub Start()
'メインスレッドに名前を付ける
Threading.Thread.CurrentThread.Name = "メインスレッド"
'別スレッドを開始する
_SubThread = New Threading.Thread( _
New Threading.ThreadStart(AddressOf SubThreadProcess))
_SubThread.Name = "サブスレッド"
_SubThread.Start()
End Sub
'別スレッドで実行する処理
Private Sub SubThreadProcess()
'処理1
Threading.Thread.Sleep(1000)
ReportProgress("処理1が終了しました。") 'この処理をメインスレッドで実行したい
'処理2
Threading.Thread.Sleep(1000)
ReportProgress("処理2が終了しました。") 'この処理をメインスレッドで実行したい
'処理3
Threading.Thread.Sleep(1000)
ReportProgress("処理3が終了しました。") 'この処理をメインスレッドで実行したい
End Sub
Private Sub ReportProgress(ByVal message As String)
'SynchronizingObjectが設定されていればInvoke、そうでなければ普通に実行
If Not _SynchronizingObject Is Nothing Then
_SynchronizingObject.Invoke( _
New OnProgressDelegate(AddressOf OnProgress), _
New Object() {message})
Else
OnProgress(message)
End If
End Sub
'OnProgressメソッド用のデリゲート
Private Delegate Sub OnProgressDelegate(ByVal message As String)
'処理の途中経過を報告するイベント
Public Event Progress(ByVal sender As Object, ByVal e As ProgressEventArgs)
'Progressイベントを発生させる
Private Sub OnProgress(ByVal message As String)
RaiseEvent Progress(Me, New ProgressEventArgs(message))
End Sub
'Progressイベントのイベント変数クラス
Public Class ProgressEventArgs
Inherits EventArgs
Private _message As String
Public ReadOnly Property Message() As String
Get
Return _message
End Get
End Property
Public Sub New(ByVal message As String)
_message = message
End Sub
End Class
End Class
|
|
- まどか
- ぬし
- 会議室デビュー日: 2005/09/06
- 投稿数: 372
- お住まい・勤務地: ますのすし管区
|
投稿日時: 2005-10-13 18:43
引用: |
|
SynchronizingObject = Meを設定する程度であれば、
コード: |
|
'OnProgressメソッド用のデリゲート
Private Delegate Sub OnProgressDelegate(ByVal message As String)
|
|
私が今おこなっているのは、Delegateをスレッドクラス側でPublic定義し
受け取りオブジェクトとDelgate参照をプロパティにしています。
コード: |
|
'Class MyTreadClass
Public Class MyCallBacksKind
Public Delegate Sub Progress(ByVal Message As String)
End Class
Public Property Reciever() As Object
Public Property ProgressHandler() As MyCallBacksKind.Progress
--------------------------------------------------------------
'Caller
objMyThread = New MyThreadClass
With objMyThread
.Reciever = Me
.ProgressHandler = New MyCallBacksKind.Progress(AddressOf MyThread_Progress)
.StartThread()
End With
Private Sub MyThread_Progress(ByVal Message As String)
End Sub
|
|
- kanai
- ベテラン
- 会議室デビュー日: 2004/09/13
- 投稿数: 98
|
投稿日時: 2005-10-13 20:14
まどかさん、ご回答ありがとうございます。
引用: |
|
私が今おこなっているのは、Delegateをスレッドクラス側でPublic定義し
受け取りオブジェクトとDelgate参照をプロパティにしています。
|
フォーム(Caller=Form1)が、クラス(MyTreadClass=Class1)に対して
受け取りオブジェクトとデリゲート参照を渡す、という理解でよろしいでしょうか?
今回、「クラス利用者(フォーム側)の実装を簡単にしたい」と考えております。
となると、「このプロパティにデリゲートを設定ください」というのは、
クラス利用者にとって若干敷居が高くなってしまうと思います。
もしこの方法で、フォームから何も情報を渡さずにクラス内部でメインスレッド
にマーシャリングできるならば、ぜひ実装したいのですが・・・。
編集:BBコードの間違いを訂正
[ メッセージ編集済み 編集者: kanai 編集日時 2005-10-13 20:15 ]
|
- 渋木宏明(ひどり)
- ぬし
- 会議室デビュー日: 2004/01/14
- 投稿数: 1155
- お住まい・勤務地: 東京
|
投稿日時: 2005-10-14 00:05
本題とあまり関係が無くてごめんなさい。
引用: |
|
このような場合、フォーム側でInvokeを使用してコントロールの操作が
メインスレッド上で行われるようにする必要がある、と理解していますが、
クラス側でInvoke相当の処理を行ってメインスレッドにマーシャリングすれば、
|
それって「マーシャリング」なんだっけ?
|
- まどか
- ぬし
- 会議室デビュー日: 2005/09/06
- 投稿数: 372
- お住まい・勤務地: ますのすし管区
|
投稿日時: 2005-10-14 00:26
引用: |
|
フォーム(Caller=Form1)が、クラス(MyTreadClass=Class1)に対して
受け取りオブジェクトとデリゲート参照を渡す、という理解でよろしいでしょうか?
今回、「クラス利用者(フォーム側)の実装を簡単にしたい」と考えております。
となると、「このプロパティにデリゲートを設定ください」というのは、
クラス利用者にとって若干敷居が高くなってしまうと思います。
|
イベントハンドラとデリゲートプロシージャを記述するのは同じ負担ですよね。
私の例はそれに加えてクラスへの明示的な割り当てが増える、位に私は思っています。
自動でプロシージャ定義が出てこないのは面倒ですけど。
まぁイベントのほうがぜんぜんわかりやすいインターフェースだとは思います。
#こっちのほうがいいというような話ではありませんので、念のため。
[quote]
それって「マーシャリング」なんだっけ?
[/quite]
私も気になってました。
あと、「フォーム側で」っていうのも。
|