現在C++/CLIでのフォームアプリケーションを作成しています
その中で更新用と描画用のスレッドを作成し、処理をさせています
ですが特定の条件時、また何もしなくても偶に描画が出来なくなります
画像の読み込みエラーを疑ったのですが描画が出来ない状態にならない場合は平常の動作をしています
また、debugモードだとエラーダイアログボックスが出ず各スレッドの中に挿入しているtrycatch構文の処理がされておらず
どこでエラーになっているかが分かりません
どなたか教えていただけないでしょうか、お願いします
環境 visualstudio2013 C++/CLI
以下ダイアログボックスの例外テキスト
************** 例外テキスト **************
System.ArgumentException: 使用されたパラメーターが有効ではありません。
場所 System.Drawing.Image.get_Flags()
場所 System.Windows.Forms.ControlPaint.IsImageTransparent(Image backgroundImage)
場所 System.Windows.Forms.Control.PaintBackground(PaintEventArgs e, Rectangle rectangle, Color backColor, Point scrollOffset)
場所 System.Windows.Forms.Control.PaintBackground(PaintEventArgs e, Rectangle rectangle)
場所 System.Windows.Forms.Control.OnPaintBackground(PaintEventArgs pevent)
場所 System.Windows.Forms.ScrollableControl.OnPaintBackground(PaintEventArgs e)
場所 System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer)
場所 System.Windows.Forms.Control.WmPaint(Message& m)
場所 System.Windows.Forms.Control.WndProc(Message& m)
場所 System.Windows.Forms.ScrollableControl.WndProc(Message& m)
場所 System.Windows.Forms.Form.WndProc(Message& m)
場所 System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
場所 System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
場所 System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
System.ArgumentException
System.ArgumentException
- 添付ファイル
-
- TheGameofLife.zip
- VC++のソースとコンテンツ(コンテンツは.exeと同じディレクトリに置く)
- (447.42 KiB) ダウンロード数: 118 回
-
- 記事: 85
- 登録日時: 9年前
- 住所: 日本
- 連絡を取る:
Re: System.ArgumentException
使用されたパラメーターが有効ではありません。
ダウンロードは面倒くさいのでソースは見ていませんが、↑のエラーは恐らくthrow系関数で投げられたものではなくてシステムから投げられたものでしょう。
try と catch 構文は、実行中にthrow関数が投げられた場合にだけ働きます。
VC++は使ってないからよく分からないけど、
VBとかだと、NULLアドレスを参照しようとするとメモリエラーになり、catchなどにも引っかかりません。
つまり、tryやcatchでは
そのエラーを無視しても正常にプログラムが実行できるという場合にしか捕まえられません。
恐らく System.~~~系の例外は捕まらないでしょう。
また、上記のエラーの原因は恐らくNULLを渡したなどがあると思います。
更に原因を挙げると、ライブラリを動的読み込みして関数をプログラムに登録する時に引数の型を間違えたなどがあります。
ダウンロードは面倒くさいのでソースは見ていませんが、↑のエラーは恐らくthrow系関数で投げられたものではなくてシステムから投げられたものでしょう。
try と catch 構文は、実行中にthrow関数が投げられた場合にだけ働きます。
VC++は使ってないからよく分からないけど、
VBとかだと、NULLアドレスを参照しようとするとメモリエラーになり、catchなどにも引っかかりません。
つまり、tryやcatchでは
そのエラーを無視しても正常にプログラムが実行できるという場合にしか捕まえられません。
恐らく System.~~~系の例外は捕まらないでしょう。
また、上記のエラーの原因は恐らくNULLを渡したなどがあると思います。
更に原因を挙げると、ライブラリを動的読み込みして関数をプログラムに登録する時に引数の型を間違えたなどがあります。
Re: System.ArgumentException
メインスレッドで発生した例外ですね。別のスレッドではなく。
main.cpp を以下のように修正してみてください。
とりあえず例外を捕まえることができるはずです。
それと気になるところが1つ。
MyForm::Render() で this->BackgroundImage = %map; してますが、
コレは絶対やっちゃダメです。
Form に限らず MFC とか WPF もそうですが、
別スレッドからの UI Control へのアクセスは基本的に禁止されています。
原因のわかりにくい不具合のもとになります。
スレッド間のメッセージ通信を使うなり Invoke() を使うなりして、
必ずメインスレッドでセットするようにしてください。
main.cpp を以下のように修正してみてください。
とりあえず例外を捕まえることができるはずです。
// 未処理例外をキャッチするイベントハンドラ
static void Application_ThreadException(Object^ sender, ThreadExceptionEventArgs^ e)
{
// このへんでブレークポイントを張る
Console::WriteLine(e->Exception);
}
[STAThreadAttribute]
int main(array<String^>^ args) {
Application::EnableVisualStyles();
Application::SetCompatibleTextRenderingDefault(false);
Application::ThreadException += gcnew System::Threading::ThreadExceptionEventHandler(&Application_ThreadException); // 追加
Application::Run(gcnew TheGameofLife::MyForm());
return 0;
}
それと気になるところが1つ。
MyForm::Render() で this->BackgroundImage = %map; してますが、
コレは絶対やっちゃダメです。
Form に限らず MFC とか WPF もそうですが、
別スレッドからの UI Control へのアクセスは基本的に禁止されています。
原因のわかりにくい不具合のもとになります。
スレッド間のメッセージ通信を使うなり Invoke() を使うなりして、
必ずメインスレッドでセットするようにしてください。
Re: System.ArgumentException
Aozora0630さん、lrikiさん御回答ありがとうございます
別スレッドからアクセスしてもコンパイラ時エラーが出なかったのでこれで良いのだと思っていました。
デリゲートを作成しInvokeメソッドを使用してメインスレッドで処理するように書き換えました
ここ以外に書き換えている場所はないのですが
Enterキーなどを押すと更新処理まで止まってしまい、何もしなくても同じエラーが出てしまいます
どこが間違っているのでしょうか
そうなのですか、trycatchに関してはエラーを取得できるという風に認識していてそこまで勉強していませんでした。Aozora0630 さんが書きました: つまり、tryやcatchでは
そのエラーを無視しても正常にプログラムが実行できるという場合にしか捕まえられません。
描画はメインで・・・ありがとうございますlriki さんが書きました: MyForm::Render() で this->BackgroundImage = %map; してますが、
コレは絶対やっちゃダメです。
Form に限らず MFC とか WPF もそうですが、
別スレッドからの UI Control へのアクセスは基本的に禁止されています。
原因のわかりにくい不具合のもとになります。
スレッド間のメッセージ通信を使うなり Invoke() を使うなりして、
必ずメインスレッドでセットするようにしてください。
別スレッドからアクセスしてもコンパイラ時エラーが出なかったのでこれで良いのだと思っていました。
デリゲートを作成しInvokeメソッドを使用してメインスレッドで処理するように書き換えました
ここ以外に書き換えている場所はないのですが
Enterキーなどを押すと更新処理まで止まってしまい、何もしなくても同じエラーが出てしまいます
どこが間違っているのでしょうか
delegate void UpDateRenderBackGroundDelegate();
// メインスレッド上でbackgroundImageにBitmapデータを貼り付ける
void UpDateRenderBackGround(){
// もしメインスレッドから呼び出しを受けたらセットを行う
this->BackgroundImage = bm;
}
// 描画処理
void Render(){
// デリゲートインスタンスの生成を行う
UpDateRenderBackGroundDelegate^ rend = gcnew UpDateRenderBackGroundDelegate(this, &MyForm::UpDateRenderBackGround);
while (!EndFlags)
{
if (life->mutex->WaitOne()){
delete bm; bm = nullptr;
bm = gcnew Bitmap(ClientSize.Width, ClientSize.Height);
g = Graphics::FromImage(bm);
g->InterpolationMode = Drawing2D::InterpolationMode::HighQualityBicubic;
life->Render(g);
Invoke(rend);
life->mutex->ReleaseMutex();
}
Thread::Sleep(15);
}
}
Re: System.ArgumentException
まだマズイですね。
1つ目。
Invoke() は delegate をメインスレッドに PostMessage() して、その delegate が処理されるまで待機します。
この PostMessage() をするとき、既に KeyDown メッセージがキューに入っていたらどうでしょうか。
UpDateRenderBackGround() より先に MyForm_KeyDown() が呼び出されるため、
life->mutex->WaitOne() でデッドロックしてしまします。
この状態は、デバッグ実行中に Enter キー等を押してアプリが固まった時、[デバッグ] > [すべて中断] すると確認できます。
(併せて [デバッグ] > [ウィンドウ] > [スレッド] も参考にしてみてください)
2つ目。
this->BackgroundImage = bm; は、ビットマップがセットされた後 Paint メッセージを Post します。
これによって、UpDateRenderBackGround() が無事終了した後、別のタイミングで Paint イベントが発生します。
セットしたビットマップを使ったウィンドウへの描画はこの Paint イベント時に行われますが、
これは排他処理が行われていないため delete bm; と同時に実行される可能性があります。
描画中にビットマップが削除されることになるため何が起こるかわかりません。
(そもそも GC のある C++/CLI で、Bitmap クラスを明示的に delete する意味は無いと思いますが)
3つ目。
ReleaseMutex() は必ず finally で実行しましょう。
もし WaitOne() ~ ReleaseMutex() 内で例外が発生した場合、ReleaseMutex() が実行されないためデッドロックに繋がります。
最後に少しコメントを。
経験上のことなので明確な根拠があるわけではありませんが、
マルチスレッドに関しては付け焼刃でコードを修正することは絶対に避けてください。
(私が「Paint イベントで排他処理を~」と言ったからとりあえず OnPaint() に WaitOne() を書いてみよう、はナシです)
ちゃんと設計の段階から「デッドロックはあり得ない」くらいまでフローを練り上げるか、
練る必要もないくらいシンプルなフローにしてからコードに落とし込んでください。
調査も設計も中途半端なままマルチスレッドを組むのは非常に危険です。
ちなみにマルチスレッド化の目的は、ほとんどの場合「レスポンスの向上」です。
ゲームの場合、例えば描画に時間がかかりすぎて1フレームの間に他の処理が行えないのであれば描画を別スレッドに分ける余地はあります。
現状で十分なレスポンスが得られているのであれば、危険を冒してまでマルチスレッド化する意味はありません。
今回はおそらく前のスレッドからの流れで勉強されているのだと読み取りましたが、
勉強目的であればこういう失敗から学んでいくのは有意義だと思います。
ただ、本番では本当にマルチスレッドが必要なのか検討した上で、じっくりと設計してみてください。
1つ目。
Invoke() は delegate をメインスレッドに PostMessage() して、その delegate が処理されるまで待機します。
この PostMessage() をするとき、既に KeyDown メッセージがキューに入っていたらどうでしょうか。
UpDateRenderBackGround() より先に MyForm_KeyDown() が呼び出されるため、
life->mutex->WaitOne() でデッドロックしてしまします。
この状態は、デバッグ実行中に Enter キー等を押してアプリが固まった時、[デバッグ] > [すべて中断] すると確認できます。
(併せて [デバッグ] > [ウィンドウ] > [スレッド] も参考にしてみてください)
2つ目。
this->BackgroundImage = bm; は、ビットマップがセットされた後 Paint メッセージを Post します。
これによって、UpDateRenderBackGround() が無事終了した後、別のタイミングで Paint イベントが発生します。
セットしたビットマップを使ったウィンドウへの描画はこの Paint イベント時に行われますが、
これは排他処理が行われていないため delete bm; と同時に実行される可能性があります。
描画中にビットマップが削除されることになるため何が起こるかわかりません。
(そもそも GC のある C++/CLI で、Bitmap クラスを明示的に delete する意味は無いと思いますが)
3つ目。
ReleaseMutex() は必ず finally で実行しましょう。
もし WaitOne() ~ ReleaseMutex() 内で例外が発生した場合、ReleaseMutex() が実行されないためデッドロックに繋がります。
最後に少しコメントを。
経験上のことなので明確な根拠があるわけではありませんが、
マルチスレッドに関しては付け焼刃でコードを修正することは絶対に避けてください。
(私が「Paint イベントで排他処理を~」と言ったからとりあえず OnPaint() に WaitOne() を書いてみよう、はナシです)
ちゃんと設計の段階から「デッドロックはあり得ない」くらいまでフローを練り上げるか、
練る必要もないくらいシンプルなフローにしてからコードに落とし込んでください。
調査も設計も中途半端なままマルチスレッドを組むのは非常に危険です。
ちなみにマルチスレッド化の目的は、ほとんどの場合「レスポンスの向上」です。
ゲームの場合、例えば描画に時間がかかりすぎて1フレームの間に他の処理が行えないのであれば描画を別スレッドに分ける余地はあります。
現状で十分なレスポンスが得られているのであれば、危険を冒してまでマルチスレッド化する意味はありません。
今回はおそらく前のスレッドからの流れで勉強されているのだと読み取りましたが、
勉強目的であればこういう失敗から学んでいくのは有意義だと思います。
ただ、本番では本当にマルチスレッドが必要なのか検討した上で、じっくりと設計してみてください。
Re: System.ArgumentException
lrikiさんありがとうございます、お考えの通り前回のスレッドからこれを作っていました。lriki さんが書きました:まだマズイですね。
1つ目。
Invoke() は delegate をメインスレッドに PostMessage() して、その delegate が処理されるまで待機します。
この PostMessage() をするとき、既に KeyDown メッセージがキューに入っていたらどうでしょうか。
UpDateRenderBackGround() より先に MyForm_KeyDown() が呼び出されるため、
life->mutex->WaitOne() でデッドロックしてしまします。
この状態は、デバッグ実行中に Enter キー等を押してアプリが固まった時、[デバッグ] > [すべて中断] すると確認できます。
(併せて [デバッグ] > [ウィンドウ] > [スレッド] も参考にしてみてください)
2つ目。
this->BackgroundImage = bm; は、ビットマップがセットされた後 Paint メッセージを Post します。
これによって、UpDateRenderBackGround() が無事終了した後、別のタイミングで Paint イベントが発生します。
セットしたビットマップを使ったウィンドウへの描画はこの Paint イベント時に行われますが、
これは排他処理が行われていないため delete bm; と同時に実行される可能性があります。
描画中にビットマップが削除されることになるため何が起こるかわかりません。
(そもそも GC のある C++/CLI で、Bitmap クラスを明示的に delete する意味は無いと思いますが)
3つ目。
ReleaseMutex() は必ず finally で実行しましょう。
もし WaitOne() ~ ReleaseMutex() 内で例外が発生した場合、ReleaseMutex() が実行されないためデッドロックに繋がります。
最後に少しコメントを。
経験上のことなので明確な根拠があるわけではありませんが、
マルチスレッドに関しては付け焼刃でコードを修正することは絶対に避けてください。
(私が「Paint イベントで排他処理を~」と言ったからとりあえず OnPaint() に WaitOne() を書いてみよう、はナシです)
ちゃんと設計の段階から「デッドロックはあり得ない」くらいまでフローを練り上げるか、
練る必要もないくらいシンプルなフローにしてからコードに落とし込んでください。
調査も設計も中途半端なままマルチスレッドを組むのは非常に危険です。
ちなみにマルチスレッド化の目的は、ほとんどの場合「レスポンスの向上」です。
ゲームの場合、例えば描画に時間がかかりすぎて1フレームの間に他の処理が行えないのであれば描画を別スレッドに分ける余地はあります。
現状で十分なレスポンスが得られているのであれば、危険を冒してまでマルチスレッド化する意味はありません。
今回はおそらく前のスレッドからの流れで勉強されているのだと読み取りましたが、
勉強目的であればこういう失敗から学んでいくのは有意義だと思います。
ただ、本番では本当にマルチスレッドが必要なのか検討した上で、じっくりと設計してみてください。
御指摘の内容をよく考え、実践してみます!
Re: System.ArgumentException
MSDN に「マルチスレッドでGDIオブジェクトを使うのは危険だよ」ってちゃんと書いてありますね。
「削除したときはなおさら何が起こるかわからない」ともあります。
https://msdn.microsoft.com/en-us/librar ... 85%29.aspx
根本的な話になりますが、描画を別スレッドに分けるのはやめた方がいいかもしれません。
ちなみに「gdi multithread」で検索すれば他にも情報が手に入ると思います。
「削除したときはなおさら何が起こるかわからない」ともあります。
https://msdn.microsoft.com/en-us/librar ... 85%29.aspx
根本的な話になりますが、描画を別スレッドに分けるのはやめた方がいいかもしれません。
ちなみに「gdi multithread」で検索すれば他にも情報が手に入ると思います。