DOBON.NET DOBON.NETプログラミング掲示板過去ログ

別スレッドで発行したイベントをフォームで処理する方法

環境/言語:[C#]
分類:[.NET]

皆さんはじめまして、C#初心者のものです。

いま、Visual Studio .NET C#にて、『フォームが表示されてから1000(ms)経過したら「フォルダ選択」ダイアログを表示する』、といったプログラムを試用で作成しています。

動作自体は問題なく動いたのですが、よりスマートなプログラム手法がございましたらご教授お願いします。

簡単に説明させていただくと、フォーム初期表示時(Form_Shown)にて別スレッドとしてタイマーを起動し、そのスレッドが1000(ms)経過を認知した時に、フォームクラスのイベント処理ハンドラが呼び出される仕組みになっています。

そこで、『タイマを起動する関数をデリゲートで呼び出し、更にその関数の引数にフォームクラスのイベントハンドラのデリゲートを渡す』という『複雑な作り』になってしまっています。

// フォーム
public partial class Form1 : Form
{
// OnTimePassedのデリゲート
private delegate void delegateOnTimePassed( object sender, EventArgs e );

// タイマスレッド起動のためのデリゲート
private delegate void delegateStartTimer( delegateOnTimePassed funcTimePassed );

// フォーム表示後に呼び出される。
private void Form1_Shown(object sender, EventArgs e)
{
delegateOnTimePassed dOnTimePassed = new delegateOnTimePassed( OnTimePassed );
delegateStartTimer dStartTimer = new delegateStartTimer ( StartTimer );

// タイマ開始
dStartTimer.BeginInvoke( dOnTimePassed , null, null );
}

// タイマスレッドで1000(ms)経過後に呼び出されるイベントハンドラ
private void OnTimePassed ( object sender , EventArgs e )
{
// フォルダ選択ダイアログを表示する
}

// タイマ起動関数
private void StartTimer( delegateOnTimePassed funcTimePassed )
{
TimerObject timer = new TimerObject( 1000 );

timer.EventTimer += new TimerObject.TimerHandler( funcTimePassed );
timer.Start();
}
}

// タイマクラス
public class TimerObject
{
readonly private uint mTimeNum = 0;

// 構築
public TimerObject ( uint timeNum )
{
mTimeNum = timeNum;
}

// イベントハンドラ ( mTimeNum(ms)経過後に発行される。)
public delegate void TimerHandler ( object sender , EventArgs e );

// イベント
public event TimerHandler EventTimer;

// 指定時間経過後に呼び出される。
protected virtual void OnTimer ( EventArgs e )
{
if ( EventTimer != null ) {
EventTimer( this , e );
}
}

// 開始
public void Start ()
{
Thread.Sleep( (int)mTimeNum );
OnTimer( new EventArgs() );
}
}
System.Windows.Forms.Timer を使えば UI スレッドでタイマを動かせますが、何か不都合でもありますか?
精度が気にくわないというのなら、System.Timers.Timer の方をデザイナに D&D してやってもいいでしょう。
■No26072に返信(Hongliangさんの記事)
> System.Windows.Forms.Timer を使えば UI スレッドでタイマを動かせますが、何か不都合でもありますか?
> 精度が気にくわないというのなら、System.Timers.Timer の方をデザイナに D&D してやってもいいでしょう。

Hongliangさん、ありがとうございました。確かにタイマの場合はForm上のコントロールを使うという手がありましたね。早速やってみます。

タイマとは別の話になるのですが、別スレッドによって起こるなんらかのタイミングを本スレッド側で認識させるには、本スレッド側で定義した関数のdelegateを、本スレッドから別スレッド起動する時に渡す、というやり方が一般的なのでしょうか?
(delegateを直接渡すか、あるいは、delegateを構造体等のメンバに入れて渡す等、手段はたくさんありますが...)
> タイマとは別の話になるのですが、別スレッドによって起こるなんらかのタイミングを本スレッド側で認識させるには、本スレッド側で定義した関数のdelegateを、本スレッドから別スレッド起動する時に渡す、というやり方が一般的なのでしょうか?
デリゲートだろうがスレッドには関係ありません。デリゲートインスタンスがどのスレッドで作られていても、A スレッドがそのデリゲートを実行したら、デリゲートに登録されたメソッドは A スレッドで実行されます。
一般的には、処理完了イベントを定義し、スレッド作成時にそのイベントにハンドラを登録、ワーカースレッドは最後に処理完了イベントを実行、そしてイベントハンドラ内で Control.Invoke/BeginInvoke を呼び出す、という形でしょう。
他には System.Timers.Timer などが使っている ISynchronizeObject パターン、BackgroundWorker などが使っている AsyncOperation パターン(イベントベースの非同期パターン)などがありますね。
■No26078に返信(Hongliangさんの記事)
> デリゲートだろうがスレッドには関係ありません。デリゲートインスタンスがどのスレッドで作られていても、A スレッドがそのデリゲートを実行したら、デリゲートに登録されたメソッドは A スレッドで実行されます。
> 一般的には、処理完了イベントを定義し、スレッド作成時にそのイベントにハンドラを登録、ワーカースレッドは最後に処理完了イベントを実行、そしてイベントハンドラ内で Control.Invoke/BeginInvoke を呼び出す、という形でしょう。
> 他には System.Timers.Timer などが使っている ISynchronizeObject パターン、BackgroundWorker などが使っている AsyncOperation パターン(イベントベースの非同期パターン)などがありますね。

詳細まで説明いただきありがとうございました。
レスが大変遅くなり申し訳ございません。

今私が開発しているFormプログラムには、起動後100(s)たったら「フォルダ選択」ダイアログを表示してユーザにフォルダを選ばせ、そのフォルダ配下(サブディレクトリも含む)にある(*.html;*.htm)拡張子のファイルをデータグリッドに表示する処理があります。

その際、FindFirstFile() ,FindNextFile() APIを使用してファイルを検索し、検索にヒットしたタイミングでフォームにファイルのパス名をイベントとして送信します。
フォームはイベントを受け取ってデータグリッドにそのパス名を表示するといったものです。
(当然、ファイルを検索する処理は別スレッドにしておかないと、ファイル検索中にフォームがフリーズしたように見えてしまいますね。)

こういう作りを実装するに当たり、あなたのご説明を是非参考にさせていただきます。

# 何かご助言がございましたら、またよろしくお願いします。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板