- PR -

System.Timers.Timer や WebBrowser のイベントハンドラーで NullReferenceException 等が起きてもデバッガ

1
投稿者投稿内容
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2008-07-01 17:49
Visual C# 2008 Express Edition を使って Windows Application を作っています。部品としてはサーバーベースタイマー(System.Timers.Timer) と WebBrowser コントロールを良く使っていますが、そのイベントハンドラーの中で例外が起きてもデバッガーで止まってくれないことで悩んでいます。

つぎのコードで3パターンを試しましたが、微妙に違います。

コード:
using System;
using System.Windows.Forms;

namespace Hoge
{
    public partial class Form1 : Form
    {
        private System.Timers.Timer timer = new System.Timers.Timer();

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
            timer.AutoReset = false;
            timer.Interval = double.Epsilon;
        }

        private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            string s = null;
            int a = s.Length;
        }

        private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            string s = null;
            int a = s.Length;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            timer.SynchronizingObject = this;
            timer.Start();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            timer.SynchronizingObject = null;
            timer.Start();
        }

        private void button3_Click(object sender, EventArgs e)
        {
            webBrowser1.Navigate("http://www.atmarkit.co.jp/");
        }
    }
}




(1) button1 を押すと Timer を UI スレッドで発火させる。(SynchronizingObject = this(Form のインスタンス)にする。)
このイベントハンドラー(Elapsed)内で例外が起きると、デバッガーは

コード:
    static class Program
    {
        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }



の Application.Run(new Form1()); の行を指して止まり、「TargetInvocationException はハンドルされませんでした。」というダイアログボックスが表示される。そのダイアログボックスにある「詳細の表示...」や「例外の詳細をクリップボードに追加」を押すと、スタックトレースが得られるので、NullReferenceException が起きた箇所が一応は分かる。しかし、変数の値などは分からないので多少不便。


(2) button2 を押すと Timer を UI スレッドとは別スレッドで発火させる。(SynchronizingObject = null にする。)
この場合はデバッガーは NullReferenceException が起きた行で止まるので、ウォッチウィンドウで変数の表示もでき、問題なくデバッグができる。


(3) button3 を押すと WebBrowser コントロールの DocumentCompleted イベントが起きる。
この場合はデバッガーは止まらず、出力ウィンドウに「'System.NullReferenceException' の初回例外が Hoge.exe で発生しました。」とだけそっけなく表示されるだけ。この表示に気づかないと、例外が起きたことすら分からない。


デバッガーの設定が悪いのでしょうか?ほかの環境でも、このような挙動になるのが普通なのでしょうか?もしこれが普通だとしたら、みなさまはどのようにデバッグされているのでしょうか?
ひょっとしたら Express Edition なのでデバッグに制約があるのでしょうか?ちなみに、ブレークポイントの一覧を表示する機能は Express Edition ゆえに省かれていることに最近気づきました。
rain
ぬし
会議室デビュー日: 2006/10/19
投稿数: 549
投稿日時: 2008-07-02 10:25
Visual Studio 2005 Pro. で確認してみたところ、同じ結果でした。
以下、追加で確認した事柄です。

  • [ツール]−[オプション]−[デバッグ]−[全般]の「'マイ コードのみ' 設定を有効にする」のチェックが外れていると、(2)の場合もデバッガが止まってくれませんでした。
  • [デバッグ]−[例外]−[Common Language Runtime Exceptions]−[System]−[System.NullReferenceException] のチェックを入れると、どの場合もデバッガが止まってくれました。

Express Edition で同じメニューがあるかどうかわかりませんが、参考まで。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2008-07-02 18:06
(なお、件名は「System.Timers.Timer や WebBrowser のイベントハンドラーで NullReferenceException 等が起きてもデバッガーが止まらない」と書こうとして、掲示板の制限で途切れてしまいました。)

引用:

rainさんの書き込み (2008-07-02 10:25) より:

  • [ツール]−[オプション]−[デバッグ]−[全般]の「'マイ コードのみ' 設定を有効にする」のチェックが外れていると、(2)の場合もデバッガが止まってくれませんでした。



ありがとうございます。「マイ コードのみ」のオン・オフを試してみました。確かに動きが変わりますね。

引用:

rainさんの書き込み (2008-07-02 10:25) より:

  • [デバッグ]−[例外]−[Common Language Runtime Exceptions]−[System]−[System.NullReferenceException] のチェックを入れると、どの場合もデバッガが止まってくれました。

Express Edition で同じメニューがあるかどうかわかりませんが、参考まで。


残念ながら Visual C# 2008 Express Edition は、この設定ができないようです。
この設定は、昔のバージョンの、Visual C# 2005 Express Edition では、見かけたような記憶がうっすらとありますが、あいまいです。(アンインストールしてしまったのですぐに確かめることはできません。)
ただ、私が今作っているプログラムは、コードの都合で、昔のバージョンに戻ることがもはやできなさそうなので、有料版を買うか、あるいは、ケチって、タイマーは UI スレッドを使わないようにして、DocumentCompleted のコンテキストではコードを実行せず、タイマーなどを使って別にコンテキストを作ってそこでコードを実行するなどの小細工をするかで対処することになりそうです。(すなわち3パターン中の button2 と同じ状況になるようにプログラムを変更する。)

[ メッセージ編集済み 編集者: unibon 編集日時 2008-07-02 18:07 ]

[ メッセージ編集済み 編集者: unibon 編集日時 2008-07-02 18:07 ]
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2008-07-05 17:38
その後の経過報告をします。

ふと System.Timers.Timer ではなく System.Windows.Forms.Timer ( http://www.atmarkit.co.jp/fdotnet/dotnettips/372formstimer/formstimer.html の中の分類における「Windowsタイマ」)を使うと、UI スレッドで動き、なおかつ、デバッガーでもちゃんと止まることを知りました。

私は今まで、System.Windows.Forms.Timer は大昔の VB(6以下)の Timer コントロールの互換品みたいなものだと思っていたので、あまり積極的に使う気にはなれませんでした。イベントやメソッドの名称が VB と同じで、アーキテクチャーも洗練されていないし、Interval の最小値が1ミリ秒というのも実使用上は問題ないものの変な制約だと思っていました。
しかし、UI スレッドで動かしたい場合はこれを使ったほうが良いのかなと思います。少なくとも System.Timers.Timer を使って SynchronizingObject = フォームインスタンス、とするよりは素直で良さそうな気がします。VB と違って System.Windows.Forms.Timer はフォームに貼らなくても使えるので、それほど制約もなさそうです。

また、WebBrowser の DocumentCompleted の中に入ったら、すぐに System.Windows.Forms.Timer を Interval = 1ミリ秒で起動して、やりたい処理はその中でやれば、DocumentCompleted のデバッガーの問題も解決できそうです。
1

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