- PR -

【C#】別スレッドからのFormコントロール操作

投稿者投稿内容
Makoto
大ベテラン
会議室デビュー日: 2004/03/31
投稿数: 133
投稿日時: 2005-11-07 14:14
いつもお世話になっております。

別スレッドからのFormコントロール操作に関して教えてください。

下記のようなケースはどのように実装するのが良いのでしょうか?

調べた限りでは、下記案がありそうですが、
案1,2では、ワーカスレッドの処理完了にForm側が同期してしまいNGです。
また案3は、CallBack内でFormコントロールを操作させたとして
スレッドセーフなのか?という疑問を持っています...

 案1:Control::Invoke()メソッドを使用
 案2:Control::BeginInvoke()/EndInvoke()メソッドを使用
 案3:Control::BeginInvoke()/EndInvoke()メソッドを使用しつつ、
    CallBack処理で実装する。

●処理シーケンス

@ユーザが、Form上のボタンクリック

Aボタンクリック関数内で、ワーカスレッドを生成し重い処理を実施する

Bボタンクリック関数を抜ける。
|※Formとしては、処理を何もしていない。

Cワーカスレッドで処理が完了する。

Dワーカスレッドの実行結果を、Form上に表示する。
 (別スレッドからのコントロール操作)

やりたいことのポイントですが、
・ワーカスレッドの実行完了を待たずに、Formは処理を完了する。
 (ユーザから見て、Formをブロックさせたくない。)

ついでですが、上記案1〜3では、ワーカスレッド側でメッセージボックスを
表示すると、(Formスレッド側で実施していないので、)
メッセージボックスがFormの裏側に回ってしまったりします。
これって、どうしようもないのでしょうか?

C++でのPostMessage系の処理って、ないのでしょうか?

以上、よろしくお願いいたします。
nodera
大ベテラン
会議室デビュー日: 2003/09/08
投稿数: 200
投稿日時: 2005-11-07 15:58
こんにちは。

Windowハンドルをもったコントロールを操作するには、操作する瞬間だけでもWindowハンドルをもったスレッドと同期させる必要があるので、完全に非同期で画面の内容を書き換えることはできません。

この場合、(5)の処理も重いんでしょうか?
(例えば、数千件のレコードをリスト表示するとか)

もし(5)の処理をループで回して書き換えているのであれば、ループの中にDoEventsを記述してやれば(もち、Windowハンドルをもったスレッド側)、キューに溜まったメッセージを処理するので、非同期で動いているようにみせることはできます。それじゃ、だめ?
kanai
ベテラン
会議室デビュー日: 2004/09/13
投稿数: 98
投稿日時: 2005-11-07 16:24
以前私が立てたスレッドでは、

・別スレッドの処理を別のクラス(スレッドクラス)に分離
・スレッドクラスで、SynchronizingObjectプロパティを実装
・スレッドの処理が完了したら、イベントを発生させる
 ->イベント内で、SynchronizingObjectが設定されていたらInvoke
・スレッドからのメッセージは、イベント変数に格納
・フォーム側でイベントを処理(メッセージボックスを表示、など)

という形になりました。

VBですが、こんな感じでいかがでしょうか。

コード:
'フォーム
Public Class Form1
    Inherits System.Windows.Forms.Form

#Region " Windows フォーム デザイナで生成されたコード "
    (省略)
#End Region

    Private WithEvents worker As New WorkerThreadClass

    Private Sub Button1_Click( _
        ByVal sender As System.Object, _
        ByVal e As System.EventArgs) _
    Handles Button1.Click
        worker.SynchronizingObject = Me
        worker.Start()
    End Sub

    Private Sub worker_Completed( _
        ByVal sender As Object, _
        ByVal e As WorkerThreadClass.CompletedEventArgs) _
    Handles worker.Completed
        MessageBox.Show(e.Message)
    End Sub
End Class

'ワーカスレッドクラス
Public Class WorkerThreadClass

    'スレッドオブジェクト
    Private _WorkerThread 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()
        _WorkerThread = _
            New Threading.Thread(New Threading.ThreadStart(AddressOf HeavyProcess))
        _WorkerThread.Start()
    End Sub

    'ワーカスレッドで実行する処理
    Private Sub HeavyProcess()
        Threading.Thread.Sleep(3000)
        OnCompleted(New CompletedEventArgs("重い処理は完了しました。"))
    End Sub

    '完了イベント
    Public Event Completed(ByVal sender As Object, ByVal e As CompletedEventArgs)

    '完了イベントを発生させる
    Private Sub OnCompleted(ByVal e As CompletedEventArgs)
        'SynchronizingObjectが設定されていて、InvokeRequiredがTrueならInvoke
        If ((Not CompletedEvent Is Nothing) AndAlso (Not _SynchronizingObject Is Nothing) AndAlso _SynchronizingObject.InvokeRequired) Then
            _SynchronizingObject.BeginInvoke(CompletedEvent, New Object() {Me, e})
        Else
            RaiseEvent Completed(Me, e)
        End If
    End Sub

    '完了イベント変数
    Public Class CompletedEventArgs
        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

Makoto
大ベテラン
会議室デビュー日: 2004/03/31
投稿数: 133
投稿日時: 2005-11-07 17:27
みなさま回答ありがとうございます。

DoEventはFormの処理が重くなるらしい!?ようなので、
できれば使用を避けたいと考えています。
またVB.Netは、イベントまでいってしまうと、
正しく読めている自信がありません...
(ずっと、C++、C#で開発だったもので...、でも非常に参考になっています。)

ちなみに、回答を頂いている間に下記のようなコードを書いてみました。
(Invokeで実行した処理は、Form側のスレッドで処理されているようです。)

ただ、『ワーカスレッド上からForm::Invoke関数を呼出しても安全なのか』
ということが引っかかっています。
これでいいのでしょうか?

parent.Invoke(new SetFocusDelegate(parent.SetFocus));

●サンプルソース

//Invoke用のデリゲート
delegate void SetFocusDelegate();

//フォームクラス
public class Form1 : Form
{
public void SetFocus()
{
//////////////////////////////////////
//安全に画面操作を実施できるはず!?//
//////////////////////////////////////

MessageBox.Show("Thread Completed...");
}

private void button4_Click(object sender, System.EventArgs e)
{
myThread my = new myThread(this);

Thread th = new System.Threading.Thread(new
System.Threading.ThreadStart(my.Run));
th.Start();

MessageBox.Show("Form Completed...");
}
}

//ワーカスレッドのクラス
public class myThread
{
public Form1 parent = null;


public myThread(Form1 p)
{
parent = p;
}

public void Run()
{
//重い処理
System.Threading.Thread.Sleep(5000);

parent.Invoke(new SetFocusDelegate(parent.SetFocus));
}
}

以上、お忙しいとは思いますが、よろしくお願いいたします。
れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2005-11-08 02:29
引用:

ただ、『ワーカスレッド上からForm::Invoke関数を呼出しても安全なのか』
ということが引っかかっています。
これでいいのでしょうか?



大丈夫です。
Form::InvokeやBeginInvokeはフォームを作ってないスレッドから呼び出すためにあります。
EndInvokeはフォームを作ったスレッドでも、他のスレッドでも呼び出します。

引用:

C++でのPostMessage系の処理って、ないのでしょうか?



BeginInvokeがそれです。
内部でメッセージポンプを使って
違うスレッドにメッセージをおくってます。

ちなみに、Makotoさんのように、ワーカースレッドのクラスでフォームの関数をInvokeを呼び出すのもありだと思いますが、
私はkanaiさんのようにワーカースレッドのクラスにイベントを作ることが多いです。
ワーカースレッドクラスの再利用がやりやすいんですよね。
Makoto
大ベテラン
会議室デビュー日: 2004/03/31
投稿数: 133
投稿日時: 2005-11-08 12:01
回答ありがとうございます。

おかげさまで自信をもって実装できそうです。

>ちなみに、Makotoさんのように、ワーカースレッドのクラスでフォームの関数を
>Invokeを呼び出すのもありだと思いますが、
>私はkanaiさんのようにワーカースレッドのクラスにイベントを作ることが多いです。
>ワーカースレッドクラスの再利用がやりやすいんですよね。

ところで、『ワーカスレッドクラスの再利用』って、どういうことでしょうか?
私の認識では、『ワーカスレッド』=『特定の処理を実施するスレッド』
なので抽象化して再利用性が上がるとイメージできないのですが...?
(各種イベントを識別することで、一つのスレッドクラスですべて処理する
 ⇒再利用性向上ということでしょうか?)

↑これは実装し続けていれば、いずれ気付けることなのかも知れませんね。
 今後も勉強していきます。

以上、アドバイスありがとうございました。
なちゃ
ぬし
会議室デビュー日: 2003/06/11
投稿数: 872
投稿日時: 2005-11-08 13:32
引用:

Makotoさんの書き込み (2005-11-08 12:01) より:

>ワーカースレッドクラスの再利用がやりやすいんですよね。

ところで、『ワーカスレッドクラスの再利用』って、どういうことでしょうか?
私の認識では、『ワーカスレッド』=『特定の処理を実施するスレッド』
なので抽象化して再利用性が上がるとイメージできないのですが...?


どちらかというと、
「ワーカースレッドを活用するような機能」の、コンポーネントとしての再利用性向上
というような意味合いではないですかね。
※スレッドを利用するような「機能」を、部品として扱いやすい形のクラスとして実装する
みたいな。
れい
ぬし
会議室デビュー日: 2005/11/01
投稿数: 346
投稿日時: 2005-11-08 17:23
引用:

Makotoさんの書き込み (2005-11-08 12:01) より:
ところで、『ワーカスレッドクラスの再利用』って、どういうことでしょうか?
私の認識では、『ワーカスレッド』=『特定の処理を実施するスレッド』
なので抽象化して再利用性が上がるとイメージできないのですが...?
(各種イベントを識別することで、一つのスレッドクラスですべて処理する
 ⇒再利用性向上ということでしょうか?)



『ワーカスレッド』=『特定の処理を実施するスレッド』
ですので、コマンドラインアプリケーションでの利用や、
Windowsのサービスとして実行したい場合もあるかもしれません。

その場合、ワーカースレッドクラスがFormを必要とすると、
そのままでは使えません。
kanaiさんの実装方法なら、SynchronizingObjectを設定しなければそのまま使えます。

イベントをつかうのも同じです。
スレッドから呼ばれるFormでコールバック用のメソッドを
毎回定義するのはめんどうです。

古いコードの使い方って、すぐ忘れてしまいますから。

たとえば、そんな話です。

スキルアップ/キャリアアップ(JOB@IT)