- PR -

イベントデリゲートと排他制御(マルチスレッドの場合)について

1
投稿者投稿内容
Makoto
大ベテラン
会議室デビュー日: 2004/03/31
投稿数: 133
投稿日時: 2004-05-28 18:01
いつもお世話になっております。
イベントデリゲートについて質問させてください。

AスレッドとBスレッドが存在していた場合に、
A→Bへイベントデリゲートによって処理を実行させた場合に、
(Aから、Bへイベントを通知するイメージ)

この時、B側で受信したイベントは、A、Bどちらのスレッド上で動作しているのでしょうか?

@Aが、Bの関数を呼んでいる→関数CallなのでAスレッド上で動作

AAが、Bへイベントを通知している。
 結果、Bスレッドで受信したイベントを処理する→関数CallではないのでBスレッド上で動作

この辺て、どなたかご存知の方いらっしゃいますか?
もし、@であれば関数内で処理されるデータをMutexなどを使用して排他制御をしないと守れないですし、
逆にAだとBスレッドが常に使用するので排他制御する必要はないということになると思うのですが、どうなんでしょうか?

また、排他なんですが、、
lock(this)や、lock(クラス名称)の排他機構は、関数へのアクセスを制限するもので、
関数内部で使用するオブジェクトを保護することはできないのでしょうか?
例えば、下記ではAddとDelが同時に異なったスレッドから呼ばれれば、保護したいデータ『m_ar』は、どうなるか保障できないのでしょうか?
それをlock(this)などでアクセス制限できるのでしょうか?

public class A
{
ArrayList m_ar = new ArrayList();
public void Add(Object o)
{
m_ar.Add( o );
}

public void Del(index i)
{
m_ar.Remove( i );
}
};


ご存知の方、いらっしゃいましたら、よろしくお願い致します。
なちゃ
ぬし
会議室デビュー日: 2003/06/11
投稿数: 872
投稿日時: 2004-05-28 18:46
引用:

Makotoさんの書き込み (2004-05-28 18:01) より:
AスレッドとBスレッドが存在していた場合に、
A→Bへイベントデリゲートによって処理を実行させた場合に、
(Aから、Bへイベントを通知するイメージ)


スレッドからスレッドにイベント通知というのがどういう意味か良く分かりませんが…
イベントとは、.NET Framework における通常のイベントのことですか?
引用:

この時、B側で受信したイベントは、A、Bどちらのスレッド上で動作しているのでしょうか?

@Aが、Bの関数を呼んでいる→関数CallなのでAスレッド上で動作

AAが、Bへイベントを通知している。
 結果、Bスレッドで受信したイベントを処理する→関数CallではないのでBスレッド上で動作


場合によって、あるいはイベントの実装によって違いがあったりしますが…
一言で言えば、.NET Framework におけるイベントの機構そのものは、スレッドを意識しません。
というか、イベントの「通知」は結局のところ通常は単なるデリゲート呼び出しなので、イベントハンドラも当然呼び出し側と同じスレッドになります。

しかしながら、イベントハンドラを呼び出す部分の「実装」によっては、あるスレッドに同期してイベントハンドラを呼び出す事も出来ます。
※そういうイベントを持つクラスもあります。
でもこれは、同期するようにイベントハンドラの呼び出しを行っているから同期するわけで、.NET Framework のイベントの機構によるものではありません。
引用:

lock(this)や、lock(クラス名称)の排他機構は、関数へのアクセスを制限するもので、
関数内部で使用するオブジェクトを保護することはできないのでしょうか?
例えば、下記ではAddとDelが同時に異なったスレッドから呼ばれれば、保護したいデータ『m_ar』は、どうなるか保障できないのでしょうか?
それをlock(this)などでアクセス制限できるのでしょうか?


lockは「オブジェクトを保護する」ものではありません。
あるオブジェクトに対してロックを取得します。

同一オブジェクトに対するロックは、全スレッドで一つしか同時には取得できないので、同じオブジェクトに対する lock ブロック内は、必ず一つのスレッドしか同時には実行できないことになります。

しかしながら、「オブジェクトを保護する」機能ではないので、ロックの取得をどこかで忘れれば、その部分は同時に実行されてしまう可能性があります。
単純には、対象オブジェクトにアクセスする箇所全てで、lock を使用する必要があります。
Makoto
大ベテラン
会議室デビュー日: 2004/03/31
投稿数: 133
投稿日時: 2004-05-28 19:19

回答ありがとうございます。
いくつか確認させていただきたいので、再度質問させていただきます。

@排他機構について
下記のようなmainが実行された場合に、aInstance.Add( data );を実行している間は、
『今実行しているスレッド以外ではaInstanceを操作できない』ということでしょうか?
※aInstance.Del(0)関数実行中も同様。
逆に、もしAdd関数内で、Del関数をコールするとデッドロックするということでしょうか?

public class A
{
ArrayList m_ar = new ArrayList();
public void Add(Object o)
{
lock(this)
{
m_ar.Add( o );
}
}

public void Del(index i)
{
lock(this)
{
m_ar.Remove( i );
}
}

 void static main()
{
A aInstance = new A();
int data=0;
aInstance.Add( data );
}

};

@イベントデリゲートについて

例えば、『メインのスレッドA』と『非同期ソケットの受信コールバック関数を動作させるスレッドB』がいた場合に、
Bスレッドの受信処理内で、電文を受信したことをイベントデリゲートで『メインのスレッドA』のオブジェクトの
解析関数へ通知した場合は、Bスレッドで動作しているということでしょうか?

この場合、もし『Aスレッド』、『Bスレッド』で同時にアクセスしてほしくない情報があった場合は、
Mutexなどで必ず排他するということでしょうか?

なお、VC++では、メッセージなるものが存在し、Aスレッドのオブジェクト→Bスレッドのオブジェクトへ
メッセージを通知するとB側のスレッド上で実行されます。
※このような機構は、C#ではないのでしょうか?
かずくん
ぬし
会議室デビュー日: 2003/01/08
投稿数: 759
お住まい・勤務地: 太陽系第三惑星
投稿日時: 2004-05-28 19:51
引用:

@イベントデリゲートについて


イベントデリゲートは、所詮ただのメソッドコールでしかないので、特別な実装をしていない限り、同一スレッド上で実行されます。

別のスレッドで実行させたければ、間にメッセージキューを用意し、スレッドBはキューにメッセージを登録し、スレッドBが受け取るなどのようなことをすることとなるでしょう。

ところで、もしMakotoさんの言われるメインスレッドが、.net frameworkのイベントスレッドであり、上記のようなめんどくさいことをしなくても、Control#Invoke()をデリゲータを引数に呼び出せば、簡単に別スレッドで実行させることができます。
ただし、両方のスレッドから、あるオブジェクトのインスタンスフィールドに触る場合は、同期を取ってあげる必要があるでしょう。
Makoto
大ベテラン
会議室デビュー日: 2004/03/31
投稿数: 133
投稿日時: 2004-05-28 20:47
回答ありがとうございました。

タイマタイムアウト処理−メインスレッド間でイベントデリゲートを実装してみました。
スレッド名称をそれぞれ『タイマ』、『メイン』としてイベントデリゲート側処理を実施させたところ
メイン側の通知させる関数が『タイマ』のスレッド名称で表示されました。

まさしく関数コールなんですね...

ありがとうございました。

ところで、現在作成しているアプリは、Windowsサービスで、複数の装置と非同期ソケット接続をしています。
このとき、ソケット側のCallBack関数である受信関数は、メインスレッドとは別スレッド上で動作します。
このような場合には、先ほどのVC++の例のような、
メッセージング機構を実装することは基本的に行わず、関数コールを基本として
(必要があればデータを排他制御し)実装する

というのが、C#の基本的なところなのでしょうか?

今回作成しているWindowsサービスでは、
画面が無いためControl#Invoke()は使用できそうにありません...
なちゃ
ぬし
会議室デビュー日: 2003/06/11
投稿数: 872
投稿日時: 2004-05-28 21:02
引用:

Makotoさんの書き込み (2004-05-28 19:19) より:
@排他機構について
下記のようなmainが実行された場合に、aInstance.Add( data );を実行している間は、
『今実行しているスレッド以外ではaInstanceを操作できない』ということでしょうか?
※aInstance.Del(0)関数実行中も同様。


そうなりますね。
まあ、厳密には、lockブロック内を同時には実行できないですが。
引用:

逆に、もしAdd関数内で、Del関数をコールするとデッドロックするということでしょうか?


このへんはlockステートメントや、Monitorクラスのドキュメントを読んでもらった方がいいと思います。

あるスレッドが、既に取得しているオブジェクトのロックを再度取得することはできます。ただし、同じ回数、解放しないとなりません(lock ステートメントを使っている分には意識する必要はありませんが)。
同じオブジェクトを対象とするlockブロックには、何度でも入ることができるということです。
引用:

@イベントデリゲートについて

例えば、『メインのスレッドA』と『非同期ソケットの受信コールバック関数を動作させるスレッドB』がいた場合に、
Bスレッドの受信処理内で、電文を受信したことをイベントデリゲートで『メインのスレッドA』のオブジェクトの
解析関数へ通知した場合は、Bスレッドで動作しているということでしょうか?


ちょっと語弊がある表現だと思います。基本的にオブジェクトはスレッドの持ち物ではありません。なので、「『メインのスレッドA』のオブジェクト」という表現は意味がありません。
# なんとなく、言わんとすることのイメージは分かりますが。

まあ、とにかく、通常は自分で同期する必要があるということです。
引用:

なお、VC++では、メッセージなるものが存在し、Aスレッドのオブジェクト→Bスレッドのオブジェクトへ
メッセージを通知するとB側のスレッド上で実行されます。
※このような機構は、C#ではないのでしょうか?


かずくんさんが仰るように、Invoke等を使用して、同じようなことができます。
ただし、メインのスレッドがメッセージループではない場合は、他の方法で同期する必要がありますね。

非同期読み取り系のメソッド等は、読み取り操作がスレッドプールのスレッドで行われるのが普通だと思います。この場合は普通、コールバックもスレッドプールのスレッドで呼び出されると思います。

ついでですが、コールバックは、所謂イベントではありませんよ。
概念上はイベントと読んでも差し支えないと思いますが、.NET Framework における用語で、イベントとは言いません。
この辺、言葉の使い方をある程度厳密にしないと、意味が通じなくなります。

例えば、一般的なイベント同期の仕組みが分かったとしても、コールバックも同じとは限りませんよね?
1

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