none
デバックモードで動かすとInvalidOperationExceptionが出る RRS feed

  • 質問

  • VC#2005を使ってます。組み込み系はそこそこやってますが、Windowsアプリはまったくの素人です。

    質問です。WindowsのFormを作っており、「デバックなしで実行」をすると問題なく動くのですが「デバック開始」で実行するとInvalidOperationExceptionが出ます。どうやら、ライブラリ側で用意したクラスのメソッドを使うとこのようなエラーが出るようですが回避方法を教えてください。

    例外が発生した箇所は

    textBox1.Text = myData.ToString();

    という感じです。また、

    pictureBox1.Height = myDataHeight;

    という感じで、単にプロパティを変更しようとしても出ます。(変更しても出ないコンポーネントもある。)

    各コンポーネントやライブラリのクラスはとくにいじってません。VC#2003のときはこのような例外は発生しなかったと記憶しています。

    スレッド云々という話のようですが、できればスレッドやデリゲートなどを駆使しなくても解決できる方法があればと思っています。(Windowsアプリはあくまで組み込み開発のサポート的なもので行っているので、あまり力を入れたくない、という感じ。力を入れないと組めないというのであればがんばります。マイコンで計算した結果をPCへ転送して、グラフィック表示、というのをやろうとしてます。)

    2006年4月7日 5:38

回答

  • 囚人さん、

    回答頂きどうもありがとうございます。教えていただいたページ&その他インターネット情報を見て下記にて問題なく動きました。(デバックモードでも例外が出なくなった。(スレッドセーフになった?))

    <原因>

    SerialPortコンポーネントのDataReceivedイベントは必ず別スレッドで立ち上がる。VS2003までは別スレッドからコンポーネントのメンバにアクセスしても見逃されていたが、VS2005のデバックで開始では例外になる。デバック無しで開始だと見逃される。という感じでしょうか?

    ---OKなプログラム---

    namespace test2_WinApp
    {
        public partial class Form1 : Form
        {
            private byte[] sndData;
            private byte[] rcvData;

            public Form1()
            {
                InitializeComponent();
                serialPort1.Open();
            }

            //ボタンクリックイベント
            private void button1_Click(object sender, EventArgs e)
            {
                sndData = new byte[1];
                sndData[0] = 0;
                serialPort1.Write(sndData, 0, 1);
            }

            //シリアル通信受信イベント
            private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
            {
                rcvData = new byte[1];
                serialPort1.Read(rcvData, 0, 1);
    //            textBox1.Text = rcvData[0].ToString();    //DataReceived は別スレッドで動くのでこれはNG
                SetText(rcvData[0].ToString());
            }

            delegate void myTextSet(string text);

            private void SetText(string text)
            {
                if (this.textBox1.InvokeRequired)
                {
                    myTextSet d = new myTextSet(SetText);
                    this.Invoke(d, new object[] { text });
                }
                else
                {
                    this.textBox1.Text = text;
                }
            }


        }
    }

    ---OKなプログラムおわり---

    いままで、デリゲートをつかわなくても動いていたものが、こんなにコードを追加しないといけないということにちょっと抵抗がありますが、そうゆうもんなんだと理解することにします。できれば、初心者向けに、チェックがゆるーいモードを環境設定などでできるようにできればいいのでは?と思いました。

    (しかし、おかげでデリゲートについてまじめに勉強したのでぼんやりとデリゲートがわかってきました。)

    2006年4月7日 14:17

すべての返信


  • VC#2003のときはこのような例外は発生しなかったと記憶しています。

    .NET1.1 で作ったアセンブリをそのまま使おうとしているからかな?
    それがそのまま .NET2.0 環境で動こうとしていて、互換性に問題があったのではないでしょうか。
    .NET2.0 環境で再ビルドしてみてはいかがでしょうか。
    2006年4月7日 6:54
  • その設定はワーカスレッド側から行われているのですか?

    スレッドまたぎは別の例外が出るはずです。

    もうすこしエラーメッセージなり、スタックトレースなり出せませんか?

    2006年4月7日 6:55
  • Takaです。

    すいません、ここ、一度書きましたが下の方が質問がわかりやすいと思いますので消しました。下を参照願います。

    2006年4月7日 7:53
  • Takaです。

    質問がわかりずらいと思いましたので、小さくて再現性のあるプログラムを書きました。

    Form1に button1 textBox1 serialPort1 が貼り付けられています。

    button1を押すと、シリアル通信で数値の 0 を送信し、相手からは数バイトの返信が来ます。するとserialPort1の受信イベントが発生し、テキストボックスに受信した数値をstring型に直して代入してます。

    これも、「デバックなしで開始」をすると、テキストボックスに返された数値が入りますが、「デバック開始」で動かすと textBox1.Text = rcvData[0].ToString(); のところで前出の例外が発生します。

    ---以下プログラム---

    namespace test2_WinApp
    {
        public partial class Form1 : Form
        {
            private byte[] sndData;
            private byte[] rcvData;
           
            public Form1()
            {
                InitializeComponent();
                serialPort1.Open();
            }

            //ボタンクリックイベント
            private void button1_Click(object sender, EventArgs e)
            {
                sndData = new byte[1];
                sndData[0] = 0;
                serialPort1.Write(sndData, 0, 1);
            }

            //シリアル通信受信イベント
            private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
            {
                rcvData = new byte[1];
                serialPort1.Read(rcvData, 0, 1);
                textBox1.Text = rcvData[0].ToString();
            }
        }
    }

    ---プログラムおわり---

     

    ---例外の内容

    System.InvalidOperationException はハンドルされませんでした。
      Message="有効ではないスレッド間の操作: コントロールが作成されたスレッド以外のスレッドからコントロール 'textBox1' がアクセスされました。"
      Source="System.Windows.Forms"
      StackTrace:
           場所 System.Windows.Forms.Control.get_Handle()
           場所 System.Windows.Forms.Control.set_WindowText(String value)
           場所 System.Windows.Forms.TextBoxBase.set_WindowText(String value)
           場所 System.Windows.Forms.Control.set_Text(String value)
           場所 System.Windows.Forms.TextBoxBase.set_Text(String value)
           場所 System.Windows.Forms.TextBox.set_Text(String value)
           場所 test2_WinApp.Form1.serialPort1_DataReceived(Object sender, SerialDataReceivedEventArgs e) 場所 C:\Documents and Settings\AG3\My Documents\Visual Studio 2005\Projects\test2_WinApp\test2_WinApp\Form1.cs:行 35
           場所 System.IO.Ports.SerialPort.CatchReceivedEvents(Object src, SerialDataReceivedEventArgs e)
           場所 System.IO.Ports.SerialStream.EventLoopRunner.CallReceiveEvents(Object state)
           場所 System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
           場所 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
           場所 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)

    ---例外の内容おわり---

    (初めからこのように書けばよかったですね。すいません。)

    2006年4月7日 8:22
  • 囚人さん、

    回答頂きどうもありがとうございます。教えていただいたページ&その他インターネット情報を見て下記にて問題なく動きました。(デバックモードでも例外が出なくなった。(スレッドセーフになった?))

    <原因>

    SerialPortコンポーネントのDataReceivedイベントは必ず別スレッドで立ち上がる。VS2003までは別スレッドからコンポーネントのメンバにアクセスしても見逃されていたが、VS2005のデバックで開始では例外になる。デバック無しで開始だと見逃される。という感じでしょうか?

    ---OKなプログラム---

    namespace test2_WinApp
    {
        public partial class Form1 : Form
        {
            private byte[] sndData;
            private byte[] rcvData;

            public Form1()
            {
                InitializeComponent();
                serialPort1.Open();
            }

            //ボタンクリックイベント
            private void button1_Click(object sender, EventArgs e)
            {
                sndData = new byte[1];
                sndData[0] = 0;
                serialPort1.Write(sndData, 0, 1);
            }

            //シリアル通信受信イベント
            private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
            {
                rcvData = new byte[1];
                serialPort1.Read(rcvData, 0, 1);
    //            textBox1.Text = rcvData[0].ToString();    //DataReceived は別スレッドで動くのでこれはNG
                SetText(rcvData[0].ToString());
            }

            delegate void myTextSet(string text);

            private void SetText(string text)
            {
                if (this.textBox1.InvokeRequired)
                {
                    myTextSet d = new myTextSet(SetText);
                    this.Invoke(d, new object[] { text });
                }
                else
                {
                    this.textBox1.Text = text;
                }
            }


        }
    }

    ---OKなプログラムおわり---

    いままで、デリゲートをつかわなくても動いていたものが、こんなにコードを追加しないといけないということにちょっと抵抗がありますが、そうゆうもんなんだと理解することにします。できれば、初心者向けに、チェックがゆるーいモードを環境設定などでできるようにできればいいのでは?と思いました。

    (しかし、おかげでデリゲートについてまじめに勉強したのでぼんやりとデリゲートがわかってきました。)

    2006年4月7日 14:17
  •  Taka1919 さんからの引用

    いままで、デリゲートをつかわなくても動いていたものが、こんなにコードを追加しないといけないということにちょっと抵抗がありますが、そうゆうもんなんだと理解することにします。できれば、初心者向けに、チェックがゆるーいモードを環境設定などでできるようにできればいいのでは?と思いました。

    というよりも動作を保障しないコードを書いていたわけなので偶々動いていたと解釈する

    べきだと思います。実際には保障されていないコードでも偶々動いてしまうケースというのは

    割りとある話なので動いたからOKという発想は危険なのだと思います。

    たとえば、MSが公式にはそういう方法はとらないでくれと書いている方法を

    現状のライブラリのソース等を解析して問題なく動くはずだからと使っていた場合、

    MSがその方法を使えないような改変をしたとしても文句を言える余地はありませんよね。

    現在使える方法だから大丈夫なのではなく、きちんと公式に認められた方法で

    組み立てるようにする方が安全ですし、その範疇で組まれたソフトが動かないような

    改変を告知無しにMSが行えば、クレームをつけることも可能だと思うので

    その辺のところは考えに入れておいた方が良いと思います。

    (特にお客様に提供するようなプログラムの場合は特に)

     

    2006年4月13日 7:57
  •  Taka1919 さんからの引用

    できれば、初心者向けに、チェックがゆるーいモードを環境設定などでできるようにできればいいのでは?と思いました。

    例外発生時に Visual C# 2005 に「トラブルシューティングのヒント」というのが表示されていたかもしれませんが、そこからリンクされている以下のページに方法が書かれています。

    方法 : Windows フォーム コントロールのスレッド セーフな呼び出しを行う

    この例外を無効にするには、CheckForIllegalCrossThreadCalls プロパティの値を false に設定します。このように設定すると、コントロールは、Visual Studio 2003 で実行した場合と同じように動作します。

    デバッグ実行した場合と、通常実行した場合でこの値の規定値が異なります。結果が異なるのはそういう理由でしょう。

    2006年4月17日 12:24
  • 本題と全然関係ないですが、今後のために指摘しておきます。

    デバッ ではなく デバッ(DEBUG) です。

    参考:http://kw.allabout.co.jp/glossary/g_pc/w006947.htm

    # なんで、デバックっていっちゃうんだろうか?(本当に間違えている人は多いです)

    2006年4月17日 14:50