- PR -

(C#2.0)別スレッドからフォームを制御

投稿者投稿内容
一郎
ぬし
会議室デビュー日: 2002/10/11
投稿数: 1081
投稿日時: 2007-03-14 13:34
引用:

紫苑さんの書き込み (2007-03-14 12:18) より:
イベントは解決方法のために使用したのではなく、別スレッドからの
処理状況通知のために使用しています。


よこけんさんがおっしゃってますが、イベントを発生させてもイベントハンドラはその別スレッドの方で実行されますので、「別スレッドからの処理状況通知」には使えないんですよね。
解決策としては、Invokeでデリゲートを呼び出すメソッドを実装し、スレッドメソッド() 内でイベントを発生させる代わりにそれを呼び出すように変更するのが良いかと思います。

どうしても、イベントを発生させた時に別スレッドでイベントハンドラを実行したいというなら、方法が無いわけでもないんですが…。といってもC#じゃ無理ですけど。
コード:

SetLabelEventHandler^ _SetLabel;

event SetLabelEventHandler^ SetLabel{
[MethodImpl(MethodImplOptions::Synchronized)]void add(SetLabelEventHandler^ d) { _SetLabel += d; }
[MethodImpl(MethodImplOptions::Synchronized)]void remove(SetLabelEventHandler^ d) { _SetLabel -= d; }
void raise(SetLabelEventArgs^ e) { Invoke(_SetLabel, e); }
};


C++/CLIです。C#ではイベントにaddとremoveアクセッサしか定義できませんが、C++/CLIではその他raiseアクセッサといってイベントが発生した時に実行されるアクセッサ関数を定義できます。
ここでInvokeしてやれば、コントロールを保有しているスレッドでイベントハンドラが実行されます。


[ メッセージ編集済み 編集者: 一郎 編集日時 2007-03-14 13:35 ]
紫苑
会議室デビュー日: 2007/02/15
投稿数: 17
投稿日時: 2007-03-15 09:22
一郎さん回答ありがとうございます。

とても勉強になります^^
ひとつ教えて頂きたいのですが、
下記のようなやり方で、メインプロセスにイベントを発生させるやり方は、何か問題が
ありますか?「おっできた」って感じで使っちゃっていたのですが^^;
簡略版ですが、以下のような感じです。

public class Form1 : System.Windows.Forms.Form
{
 private ThreadClass[] mThread = new ThreadClass[5];

 private void Form1_Load(object sender, System.EventArgs e)
 {
  for(nIdx = 0; nIdx < 5; nIdx++)
  {
   this.mThread[nIdx] = new ThreadClass();
   this.mThread[nIdx].ThreadName = "Thread" + nIdx.ToString();
   this.mThread[nIdx].SetLabel += new ThreadClass.SetLabelEventHandler(OnSetLabel);
   this.mThread[nIdx].Run();
  }
 }

 private void OnSetLabel(SetLabelEventArgs e)
 {
  Label1.text = e.SetString;
 }
}

public class ThreadClass
{
 private string msThreadName;
 public string ThreadName
 {
  get{return this.msThreadName;}
  set{this.msThreadName = value;}
 }

 // SetLabelイベント
 public delegate void SetLabelEventHandler(SetLabelEventArgs e);
 public event SetLabelEventHandler SetLabel;

 protected virtual void OnSetLabelEvent(SetLabelEventArgs e)
 {
  if (SetLabel != null) SetLabel(e);
 }

 // スレッドの実行
 public void Run()
 {
  System.Threading.Thread thread;
  try
  {
   thread = new System.Threading.Thread(new ThreadStart(ThreadMethod));
   thread.Start();
  }
  catch
  {
  }
 }

 private void ThreadMethod()
 {
  this.SetLabel(new SetLabelEventArgs("AAAAAA");
 }
}

ご教授の程、宜しくお願い致します。



[ メッセージ編集済み 編集者: 紫苑 編集日時 2007-03-15 09:27 ]
Tdnr_Sym
ぬし
会議室デビュー日: 2005/09/13
投稿数: 464
お住まい・勤務地: 明石・神戸
投稿日時: 2007-03-15 11:25
こんにちは。

引用:

紫苑さんの書き込み (2007-03-15 09:22) より:
ひとつ教えて頂きたいのですが、
下記のようなやり方で、メインプロセスにイベントを発生させるやり方は、何か問題が
ありますか?「おっできた」って感じで使っちゃっていたのですが^^;



”イベント”って何を指しているか曖昧ですが、
提示されたコードでは、メインスレッドでイベントが起きていない
(イベントハンドラがメインスレッドで実行されていない)ようですが。

正常動作しているように見えるのも、偶々だと思います。

ワーカースレッドからControl.Textプロパティへのセット処理が
ネイティブのSetWindowText関数/WM_SETTEXTメッセージを経由することにより
メインスレッド側で処理されたんでしょう。
一郎
ぬし
会議室デビュー日: 2002/10/11
投稿数: 1081
投稿日時: 2007-03-15 14:04
引用:
紫苑さんの書き込み (2007-03-15 09:22) より:


紫苑さんのソースを動作させてみたところ、VisualStudio2005のデバッグ実行をすると
「有効ではないスレッド間の操作: コントロールが作成されたスレッド以外の…」
というメッセージのInvalidOperationExceptionが出ました。
しかし、ビルドしたexeを単体で実行すると、確かに"AAAAAA"が画面に出ましたね。
紫苑さんの環境でもそうですか?VisualStudio2005のデバッグ実行では例外が出ますよね?

http://msdn2.microsoft.com/ja-jp/library/ms171728(VS.80).aspx
ここに、
引用:

アプリケーションをデバッガで実行しているときに、コントロールの作成元以外のスレッドがそのコントロールの呼び出しを試みると、デバッガが InvalidOperationException を発生させ(ます)。
この例外はデバッグ時には確実に発生し、状況によっては実行時にも発生します。この問題を確認したときは、必ず解決してください。


と書かれています。
動作環境や現在のフレームワークの実装の仕方等によりたまたま動いているだけですので、お客様の所に持って行ったら動かないとか、パッチを当てたら動かないとか、あるいは紫苑さんの環境で明日は動かないかもしれません。

例外が出ないからこそプログラマが気を付けてコーディングしないといけませんね。
紫苑
会議室デビュー日: 2007/02/15
投稿数: 17
投稿日時: 2007-03-15 15:24
お世話になっております。

一郎さんがおっしゃるとおりの現象が起こったので
このスレッドを作成しました^^;(最初の投稿を見てください)
最初から、ここまで公開して質問するべきでした。申し訳ありません。

Tdnr_Symさん、回答ありがとうございます。
勘違いをしていました・・・

最初の投稿の内容に戻るのですが、このような作りの場合で
Invokeを使用して、「有効ではないスレッド間の操作」を回避する
方法はあるのでしょうか?

一郎さんが以前におっしゃったやり方で、とりあえずは例外の回避ができたのですが、
問題があるのか無いのか判断ができずにいます・・・
引用:

コード:
private void OnSetLabel(SetLabelEventArgs e)
{
Invoke(new SetLabelEventHandler(OnSetLabel2), e);
}

private void OnSetLabel2(SetLabelEventArgs e)
{
label1.Text = e.SetString;
}







[ メッセージ編集済み 編集者: 紫苑 編集日時 2007-03-15 15:25 ]
よねKEN
ぬし
会議室デビュー日: 2003/08/23
投稿数: 472
投稿日時: 2007-03-15 15:59
引用:

よねKENさんの書き込み (2007-03-14 11:36) より:
this.SetLabelという呼び出し箇所を
ControlクラスのInvokeメソッド経由に書き換えればよいと思います。

余談ですが、スレッドを起動するクラスと画面のクラスとを別々にする場合は、
ProcessクラスのSynchronizingObjectプロパティのように実装するのがよいでしょう。
(SynchronizingObjectにはコントロールをセットして使います。
Processクラス内部ではSynchronizingObjectに設定されたオブジェクトの
Invoke系メソッド経由でスレッドからのイベントを起動します)



と書いてますが・・・。
一郎
ぬし
会議室デビュー日: 2002/10/11
投稿数: 1081
投稿日時: 2007-03-15 17:02
イベントを使っている理由が分かりました。紫苑さんは
「別スレッドから処理状況通知」
っておっしゃってましたが、正確には
「別クラスから(画面クラスへ)の処理状態通知」
ってことですよね。で、その別クラス(ThreadClass)がスレッドを生成しているということでしょう。
これはイベントを使ってもいい場面だと思います。
初め提示されたソースでは、フォームの中でスレッドを作ったりイベントを発生させたりしているように見えたので「イベントを使う場面じゃないよなぁ」と思ってました。

イベントというのは基本的に発生時にどのメソッドが動くかは発生元自身は意識しないものですので、今回の例でもThreadClassの方でInvokeをするのはおかしい、といいたい所ですが、別スレッドを作ってどうのこうのとしているのはThreadClassの方ですので、スレッドを扱っているThreadClassの方が対応するべきだ、という考え方もあるかと思います。

私だったら、ThreadClass.SetLabelイベントの説明に、
「このイベントは新しいスレッド上で発生する可能性があります。このイベントハンドラでフォーム等のコントロールを扱いたい場合はそれを所有しているスレッド上で行うようにしてください。」
などと書いておいて、画面クラス上のSetLabelイベントハンドラでInvokeする……かなぁ。
ちょっと今回は確信が持てないですね。これでいいのかどうか。

コード書いてみました。
コード:

public class SetLabelEventArgs : EventArgs
{
public readonly string SetString;
public SetLabelEventArgs(string setString) { SetString = setString; }
}

public class ThreadClass
{
public event EventHandler<SetLabelEventArgs> SetLabel;

protected virtual void OnSetLabel(SetLabelEventArgs e)
{
if (SetLabel != null) SetLabel(this, e);
}

public void Run()
{
Thread thread = new Thread( delegate()
{
OnSetLabel(new SetLabelEventArgs("AAAAAA"));
}
);
thread.Start();
}
}

public partial class Form1 : Form
{
private ThreadClass[] mThread = new ThreadClass[5];

private void button1_Click(object sender, EventArgs e)
{
for (int nIdx = 0; nIdx < mThread.Length; nIdx++)
{
this.mThread[nIdx] = new ThreadClass();
this.mThread[nIdx].SetLabel += new EventHandler<SetLabelEventArgs>(mThread_SetLabel);
this.mThread[nIdx].Run();
}
}

private void mThread_SetLabel(object sender, SetLabelEventArgs e)
{
Invoke(new MethodInvoker( delegate()
{
label1.Text = e.SetString;
}
));
}
}


匿名メソッドなどをふんだんに使用した贅沢な一品です。ご堪能ください。
(せっかくのC#2.0ですからね。そういえばこのスレッドのタイトルC#2005ってなってますけど、C#2005ではなくてC#2.0ですね。VBはVB2005ですけど。)

[ メッセージ編集済み 編集者: 一郎 編集日時 2007-03-15 17:10 ]
紫苑
会議室デビュー日: 2007/02/15
投稿数: 17
投稿日時: 2007-03-15 17:38
お世話になっております。

一郎さん、レスありがとうございます。

引用:

一郎さんの書き込み (2007-03-15 17:02) より:
イベントを使っている理由が分かりました。紫苑さんは
「別スレッドから処理状況通知」
っておっしゃってましたが、正確には
「別クラスから(画面クラスへ)の処理状態通知」
ってことですよね。で、その別クラス(ThreadClass)がスレッドを生成しているということでしょう。
これはイベントを使ってもいい場面だと思います。
初め提示されたソースでは、フォームの中でスレッドを作ったりイベントを発生させたりしているように見えたので「イベントを使う場面じゃないよなぁ」と思ってました。



別クラスか・・・そうですね、説明が『不十分+間違って』いたんですね・・・
ご迷惑をおかけしました。

引用:

一郎さんの書き込み (2007-03-15 17:02) より:
匿名メソッドなどをふんだんに使用した贅沢な一品です。ご堪能ください。
(せっかくのC#2.0ですからね。そういえばこのスレッドのタイトルC#2005ってなってますけど、C#2005ではなくてC#2.0ですね。VBはVB2005ですけど。)



参考にさせて頂きます。本当にありがとうございます^^
(タイトルって修正できないですよね・・・)

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