C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[5]: :


(過去ログ 111 を表示中)

[トピック内 14 記事 (1 - 14 表示)]  << 0 >>

■65679 / inTopicNo.1)  ExecutionEngineExceptionについて
  
□投稿者/ もんた (1回)-(2013/03/14(Thu) 09:38:55)

分類:[.NET 全般] 

2013/03/14(Thu) 09:53:02 編集(投稿者)
2013/03/14(Thu) 09:42:43 編集(投稿者)

こんにちは、もんたです。

マルチスレッドのアプリを2台それぞれのマシンで起動していたところ、
片方のマシンでExecutionEngineExceptionというエラーが発生してしまいました。
致命的なエラーらしくスタックトレースも表示されません。
再現性もなく、1時間で発生することもあれば10時間でも発生しないことがあります。
そこで質問なのですが、そもそもこのエラーはコードが原因で発生することはあるのでしょうか?
また、発生するとすればどういった場合でしょうか?
ちなみに、メモリが原因とも思いWindows7のメモリ診断をしましたが問題ありませんでした。

解決策などをご回答頂ければ幸いです。

【環境】
Windows7
.Net 4.5
VS2012
C#、WPF
引用返信 編集キー/
■65681 / inTopicNo.2)  Re[1]: ExecutionEngineExceptionについて
□投稿者/ 774RR (61回)-(2013/03/14(Thu) 10:15:14)
> そもそもこのエラーはコードが原因で発生することはあるのでしょうか
Yes 。 というよりむしろ、プログラムのバグが原因で発生するものだ。
プログラムのバグで .NET ライブラリ内部の変数を壊しているなどが原因。

俺は manage 系は詳しくないのでアレだが、過去に ExecutionEngineException を出した経験から言うと

1. unmanage/manage を連携動作させて、かつ、unmanage/manage 連携部の関数・変数の宣言と定義が不一致な結果
本来アクセスすべきでないメモリにアクセスしているなどの原因で発生

2.マルチスレッドで必要な排他が入っていない結果として、変数の値が不正になってしまうことがある。
2つの変数が不可分に更新される必要があるような場合に排他が漏れると、
片方だけが更新された状態で別スレッドからアクセスされてバグってしまう等。

なんにせよほぼ間違いなく「自作しているプログラム自体のバグ」なので、デバッグするべし。
引用返信 編集キー/
■65700 / inTopicNo.3)  Re[2]: ExecutionEngineExceptionについて
□投稿者/ もんた (2回)-(2013/03/14(Thu) 14:01:26)
>774RR様
ありがとうございます。
排他周りが怪しそうですね・・・。
ちなみに排他は以下のように行っていますが、問題ないでしょうか?

Dispatcher dispatcher = Dispatcher.FromThread(_thread);

lock (lockObj)
{
	DispatcherOperation op =
		dispatcher.BeginInvoke(DispatcherPriority.Send, new TestDelegate(Test.TestMethod));
	op.Wait();
}

同一クラス内に複数のlock処理がある場合、ロック用オブジェクトは使い回しても大丈夫でしょうか?
(ロック用オブジェクトはprivate staticで定義してあります)

引用返信 編集キー/
■65711 / inTopicNo.4)  Re[3]: ExecutionEngineExceptionについて
□投稿者/ 774RR (62回)-(2013/03/14(Thu) 17:20:49)
ごめん、俺そのへんの manage コードに関しては詳しくないんだ。
詳しい人のコメント待ってくれ。

ロックオブジェクトを使いまわすってことは、
本来無関係であるはずの処理が待ちに参加するってことだ。
・処理Aと処理Bは排他関係にある(CDと無関係)
・処理Cと処理Dは排他関係にある(ABと無関係)
とき、処理A中に処理Cも待つ=無駄な待ちが発生する=性能低下。
あまりいいコードではないと思う。

「今ある排他処理」に問題がなくても
「排他が必要であるにもかかわらず、入っていない」箇所の検出にはなってないので
今ある排他処理を見直しただけでは何の解決にもならないだろう。
引用返信 編集キー/
■65726 / inTopicNo.5)  Re[3]: ExecutionEngine
□投稿者/ Azulean (119回)-(2013/03/14(Thu) 23:30:01)
2013/03/14(Thu) 23:31:41 編集(投稿者)

No65700 (もんた さん) に返信
> 排他周りが怪しそうですね・・・。
> ちなみに排他は以下のように行っていますが、問題ないでしょうか?

このコード片では、よいとも悪いとも言えないのではないかと、私は思います。


> lock (lockObj)
> {
> DispatcherOperation op =
> dispatcher.BeginInvoke(DispatcherPriority.Send, new TestDelegate(Test.TestMethod));
> op.Wait();
> }

ちなみに、これは何を保護することを目的とした lock ステートメントなのでしょうか。
単なる再入防止でしょうか。


> 同一クラス内に複数のlock処理がある場合、ロック用オブジェクトは使い回しても大丈夫でしょうか?
> (ロック用オブジェクトはprivate staticで定義してあります)

これも、よい場合もあるし、悪い場合もあるという答えにならざる得ません。
保護すべきリソースに着目しつつ、排他しすぎないように考えて適切な範囲を考えて決定してください。
(対象のクラスのインスタンスが複数作られることがあるなら static は NG の可能性が高くなるが、絶対ではない)


ExecutionEngineException の原因解析は正直難しいです。
ログを仕込んで再現時の状況を解析するか、再現性を高める手段を見つけるか、徹底的にコードレビューして懸念のあるところを対策するかといったところですかね。
ログを入れて様子を見るのが手っ取り早いとは思います。
引用返信 編集キー/
■65729 / inTopicNo.6)  Re[4]: ExecutionEngine
□投稿者/ howling (242回)-(2013/03/15(Fri) 10:37:19)
No65726 (Azulean さん) に返信
>>lock (lockObj)
>>{
>> DispatcherOperation op =
>> dispatcher.BeginInvoke(DispatcherPriority.Send, new TestDelegate(Test.TestMethod));
>> op.Wait();
>>}
>
> ちなみに、これは何を保護することを目的とした lock ステートメントなのでしょうか。
> 単なる再入防止でしょうか。

横からすみません。これ、C#上で変数のアトミック化がどの程度か知らない私なので言うことなのですが、
マルチスレッドの上では似たようなコーディングをしそうな気がします。
軽く調べてみたのですが、大体変数のアトミック化は保障されているようですね。
でも、longは保障されていない?とこちらの記事にはありました。

http://d.hatena.ne.jp/siokoshou/20091003

C++で同じようなコーディングをしていたのは無駄だったのでしょうか…
それとも、C#ではそのようなコーディングをしなくても良いというだけですか?

・そのようなコーディング

1.ロック取得
2.変数に代入or変数から取得
3.ロック解除
引用返信 編集キー/
■65730 / inTopicNo.7)  Re[5]: ExecutionEngine
□投稿者/ 774RR (63回)-(2013/03/15(Fri) 11:22:01)
普通に 32bit CPU で 32bit 変数を扱う場合、読み込み操作と書込み操作は atomic だ。
(適切に align されている場合に限定)

・ VC++ において long は 32bit だから long x; があるとして
・ C# の int は 32bit だから int x; があるとして
x=1; で行われる書込み操作は atomic
std::cout << x; / Console.WriteLine(x); で行われる読み込み操作は atomic
機械語レベルで1命令なので (mov [x], eax など)

++x; では 読み込み→加工→書き込み なので non atomic
x を複数スレッドで同時アクセスしているとかならロック操作が必要
だから InterlockedIncrement みたいな機能が用意されている。

y=x; では x を読み込む→別スレッドで x が更新される→自スレッドで y を書き込む
なので 全操作は atomic だが y が更新されるとき x の値はもう違うかもしれない

32bit CPU で 64bit 変数を扱う場合は単純読み込み、単純書き込みに見えても non-atomic
VC++ の long long や C# の long は 64bit だから単純操作も non-atomic
なので 64bit 変数の atomic 性質を 32bit 環境で保証したかったらロックが必要
# スレッド間で 64bit 変数を共有しないほうがいい、ってことだ

機械語レベルで複数命令なので
mov [x].hi, 0
ここで他スレッド等が x に干渉
mov [x].lo, 1

C/C++ のビットフィールドも Read/Modify/Write になるので non-atomic
引用返信 編集キー/
■65733 / inTopicNo.8)  Re[6]: ExecutionEngine
□投稿者/ howling (243回)-(2013/03/15(Fri) 12:06:30)
No65730 (774RR さん) に返信
なるほど…しかし、それだと書き方として、
ここは64bit変数だから…ここは32bitだから…とやっていくのは抜けもありそうで怖いですね。
→だからあぁいった書き方になったのかもしれないですが、経緯の詳細はさっぱりわかってなかった

基本、何か変数を扱う用のロックを1つ作成して持っておき、
それを変数使う処理で使いまわす…なんていう形になってました。

本来は変数ごとにロックを1つ作成すべきなのかもしれないですが…それもそれでどうなんでしょう。
こうやるといいよ、といった推奨の方法などはありますか?
一般解として、でも良いのですが。今後の参考にさせてください。
引用返信 編集キー/
■65736 / inTopicNo.9)  Re[7]: ExecutionEngine
□投稿者/ 774RR (64回)-(2013/03/15(Fri) 13:09:40)
スレ乗っ取り? ExecutionEngineException とはあまり関係ない話になってきたが。

まずリンク先で示してある C# 言語仕様書の解釈はおkなんだろうか?
先のコメントで書いたのと同じことが書いてあるだけ。
・特定の型は「読み込みだけ」「書き込みだけ」は atomic
・そうでない型は「読み込みだけ」「書き込みだけ」でも atomic ぢゃない
・結果的に Read Modify Write になる処理は前者の型を使っても atomic ぢゃない
・特別に設計されたライブラリ関数には RmodW を atomic に行えるものがある
ここまでは単なる一般論で、
その「特定の型」が C# の仕様として列挙されている。

一般解としては
0.その処理が当該 CPU で原理的に atomic なのかそうでないのかを理解する
原理的に atomic な処理はロック不要。
# まあこれが簡単にできないから以下が必要なわけだけど

1.変数(の更新)にロックが必要か否かを判断する。
extern int val[];
int calc_sum(void) {
 int sum=0;
 for (int i=0; i<N; ++i) sum+=val[i];
 return sum;
}
のようなとき、たぶん
i の更新 ++i にロックは必要ない。
sum+= の更新にもロックは必要ない。
val はここでは更新されないが、ロックが必要かもしれない(いらないかもしれない)

およその判断基準は
・スレッド間で共有する変数か
・割り込みの中で更新・読み取られる変数か
こういう判断は人間にしかできない。

その上で
・その CPU にとって最適なサイズより大きい変数 (C でいう int より大きい変数:配列を含む)
・ビットフィールド
は「原理的に atomic でない処理」になる可能性が極めて高いので赤信号。
必要があれば生成されるアセンブラコードを見てでも検討する。

最終的にロックが必要な変数って、数は少ないはず。

2.ロックが必要な場合でも代替手段がある可能性がある。
++x; にロックが必要だと判断したら InterlockedIncrement(&x); に置き換えれば、ロック不要
組み込み系の CPU ではこの手の要望に応えるため専用命令が用意されていたりするので
・そもそも bitfield_variable.bit1=0; をロックせずにできる可能性がある
・ビットフィールド専用インラインアセンブラ・ライブラリ関数に置き換える
などの検討を行う。

3.真にロックが必要なら、変数1個で1ロックと考えず、論理的にロックする。
tail と head を不可分に更新するのに tail で1ロック head で1ロックなんてありえない。

ってとこだろうか。
引用返信 編集キー/
■65746 / inTopicNo.10)  Re[1]: ExecutionEngineExceptionについて
□投稿者/ howling (250回)-(2013/03/15(Fri) 18:16:44)
No65679 (もんた さん) に返信
横から質問してしまってすみません。
先程の質問については、別スレッドに移しましたので、今後はそちらに書くことになると思います。
板汚しとなる結果になってしまい、ご迷惑をお掛けしました。
また、774RRさん、ご指摘ありがとうございました。
引用返信 編集キー/
■65780 / inTopicNo.11)  Re[2]: ExecutionEngineExceptionについて
□投稿者/ もんた (3回)-(2013/03/18(Mon) 09:30:52)
>howlingさん
いえいえ、大丈夫ですよ。
問題が解決されたようなら何よりです。
しかし難しそうですね・・・笑

>774RRさん
ありがとうございます。
ひとまずロック用オブジェクトは別々に分けてみました。
他の部分に関しても排他忘れがないかチェックしてみたいと思います。

>Azuleanさん
ありがとうございます。
上記のBeginInvokeは単なる予防ですね。
それと個人的にまずそうな部分を見つけたのですが、BeginInvoke内でInvokeを呼び出すのはまずいでしょうか?
上記のBeginInvoke内でウィンドウの描画部分だけDispatcher.Invokeを呼び出すようにしています。
(WPFではUIスレッド以外からコントロールを操作できないため)

度々になってしまいますが、知恵を貸して頂ければ幸いです。
引用返信 編集キー/
■65782 / inTopicNo.12)  Re[3]: ExecutionEngineExceptionについて
□投稿者/ とっちゃん (58回)-(2013/03/18(Mon) 11:23:01)
とっちゃん さんの Web サイト
No65780 (もんた さん) に返信
> 上記のBeginInvokeは単なる予防ですね。
> それと個人的にまずそうな部分を見つけたのですが、BeginInvoke内でInvokeを呼び出すのはまずいでしょうか?
> 上記のBeginInvoke内でウィンドウの描画部分だけDispatcher.Invokeを呼び出すようにしています。
> (WPFではUIスレッド以外からコントロールを操作できないため)
>
Dispatcher.BeginInvoke() は、渡したデリゲートをDispatcherを作るときに指定したスレッドコンテキスト上で呼び出し時とは異なるタイミングで実行します(一般的にこの異なるタイミングでの実行を非同期的に実行するといいます)。

Dispatcher.Invoke() は、渡したデリゲートをDispatcherを作るときに指定したスレッドコンテキスト上で呼び出したタイミングで実行します。もちろん処理が終了するまで戻ってきません。

で、サンプル的に書かれているコードですが、

lock (lockObj)

で、渡しているこのlockObjは、呼び出す先のコンテキストと同期させたいために用意したものではありませんか?
そのタイミングで、非同期的に呼び出しを行っているにもかかわらず(BeginInvoke)
終了を待機する(op.wait)

ということをしています。
時々例外を吐き出すのとは別にデッドロックになっているタイミングがかなりの頻度で発生しうる気がするんですが。。。
ExecutionEngineException が、デッドロック時に発生するかはわかりませんが
おそらく、現状の上記処理を行うのなら、

Dispatcher.FromThread(_thread).Invoke( <TestMethodから、Invokeしている先のデリゲート> );

としてやれば、おそらくほかの処理は全く必要ないと考えます。

スレッドコンテキストに絡む排他制御はほとんどの場合5Wがきちんと把握できていないことに起因します。
いつ(when)、どこで(where)、だれが(who)、なにを(what)は(5W1HのうちのWhy,howを除く4つ)、複数同時に実行する場合非常に重要です。

もちろん、lockObj で排他制御している部分が何かわからないので本当にこれが正しいかはわかりません。
もしかしたら、単に BeginInvoke でよくて、非同期のままでいいのかもしれません。
このあたりは、なぜ(why)、どうやって(how)マルチスレッド化したのか?という部分がわからない以上、だれも回答を持ちえない
どころか、ヒントになるような情報すら提供できないといえます。

引用返信 編集キー/
■65792 / inTopicNo.13)  Re[4]:
□投稿者/ もんた (4回)-(2013/03/18(Mon) 18:03:39)
2013/03/18(Mon) 18:04:10 編集(投稿者)

>とっちゃんさん
ありがとうございます。
非常に分かりやすい説明でためになりました。
確かに整理して確認すると、自分のソースでは不要なBeginInvokeでした。
(そもそもBeginInvoke自体を呼び出すスレッドが画面ごとに作成される別スレッドなので意味がありませんでした。)

教えて頂いた方法に変更した所、多少描画は遅くなりましたが複数回の長時間運用でも無事動作しました。
(というか、FromThreadでInvoke呼び出しができたのですね・・・。)

色々ありがとうございました!
これにて解決とさせて頂きます。
解決済み
引用返信 編集キー/
■65794 / inTopicNo.14)  Re[5]: :
□投稿者/ とっちゃん (59回)-(2013/03/18(Mon) 18:59:18)
とっちゃん さんの Web サイト
No65792 (もんた さん) に返信

> 教えて頂いた方法に変更した所、多少描画は遅くなりましたが複数回の長時間運用でも無事動作しました。

多少遅くなるということは、描画処理を待ってしまうからだと思います。

別スレッド側で描画を待つ必要がないのであれば(必要性はわかりません) Invoke でななく、BeginInvoke にして
待機しない(op.Wait()を呼び出さない)ことで、スレッド化した処理が描画スレッドを待つことがなくなります。
それで、速度遅延も解消すると思います。

ただ、非同期処理にしていいかどうかはコード辺からはうかがうことができないため、判断はできません。
#一応解決しているようなのでチェックはつけておきます
解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -