none
ThreadPool内での例外を外部でcatchしてアプリケーションが落ちないようにしたい RRS feed

  • 質問

  • WPFでThreadPool内で発生した例外が内部で捕捉されない場合、どこにも例外が補足されずアプリケーションが落ちてしまいます。
    内部でcatchするのが一番ですが、ThreadPool内で発生した例外を外部でキャッチしてアプリケーションを落とさないようにする方法を知りたいと思っております。
    catchできなくとも落ちない方法だけでもかまいません。
    以下はThreadPoolで発生させた例外で落ちるサンプルコードになります。

            public MainWindow()
            {
                InitializeComponent();
    
                //補足されない例外が発生しているかの確認
                AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
    
                try
                { 
                    //ThreadPoolで実行
                    ThreadPool.QueueUserWorkItem(
                        new WaitCallback(this.foobar));
                }
                catch (Exception e)
                {
                    //ここには来ない。
                }
            }
    
            private void foobar(object state)
            {
                throw new Exception();
            }
    
            private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
            {
                Console.WriteLine(e);
            }

    UnhandledExcetionの内容:

    例外がスローされました: 'System.Exception' (Sample.exe の中)
    System.UnhandledExceptionEventArgs
    型 'System.Exception' のハンドルされていない例外が 設計用プロジェクト.exe で発生しました

    ハンドルされていない例外: System.Exception: 種類 'System.Exception' の例外がスローされました。
       場所 xxxx.MainWindow.ParseResponses(Object state) 場所 C:\xxxx\MainWindow.xaml.cs:行 61
       場所 System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
       場所 System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       場・・System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       場所 System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
       場所 System.Threading.ThreadPoolWorkQueue.Dispatch()
       場所 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

    2019年10月31日 6:03

回答

  • ThreadPool.QueueUserWorkItemではなくTaskを使えばいいです。
    Taskを作るときに TaskCreationOptions.LongRunningを指定しなければ TheadPoolを使って実行されます。

    Task task = Task.Run(() => this.foobar(null))
    .ContinueWith((t) =>
    {
        //ContinueWithでつなげば、エラーが無かったか確認できる
        //必要ならここで外部にエラーを通知すればいいです
        if (t.Exception is System.AggregateException)
        {
            var aex=(System.AggregateException) t.Exception;
            System.Diagnostics.Debug.WriteLine(aex.InnerException.StackTrace);
        }
        else if (t.Exception != null)
        {
            System.Diagnostics.Debug.WriteLine(t.Exception.StackTrace);
        }
    });

    それとも、try~catchの形式でThreadPool内のエラーをキャッチしたいのですか?
    ならばasync/awaitでTaskを使えばできます。

    private async Task Test()
    {
        try
        {
            await Task.Run(() => this.foobar(null));
        }
        catch(Exception e)
        {
        }
    }

    どうしてもThreadPool.QueueUserWorkItemでやりたいなら、きちんと処理内でキャッチしましょう。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2019年10月31日 10:43
    • 回答としてマーク yj0529 2019年11月1日 1:39
    2019年10月31日 10:42
  • 結局のところ各スレッドにおいてコールスタックの最初まで例外がcatchされなければアプリケーションは終了されるという話で、Taskは大雑把には次のような処理をしているだけのことです。
    ThreadPool.QueueUserWorkItem(() => {
        try {
            action();
        }
        catch (Exception ex) {
            this.Exception = ex;
        }
    });
    なので、必ずしもTaskじゃなくてもかまいません。まあTask以外を使う理由も特にない場合がほとんどでしょうけども。
    • 回答としてマーク yj0529 2019年11月1日 1:39
    2019年11月1日 1:15
  • そもそも論として、例外を発生させるべきではありません。発生することが分かっている場合に、catchするのではなく、例外が発生しないようコードを修正するべきです。例外の推奨事項も確認してください。

    で、そうであるならば、例外は開発者の意図しない状態ですので、未処理の場合にプロセスを終了させる、というのが.NETの設計方針のようです。

    一方、TaskはthreadPoolとは意味合いが違います。Taskはデータありきの概念です。何等かの結果とそれを得るための処理という括りです。ですので、データを得る際に発生した例外はハンドリングされる、という設計です。

    • 回答としてマーク yj0529 2019年11月1日 5:21
    2019年11月1日 2:17

すべての返信

  • ThreadPool.QueueUserWorkItemではなくTaskを使えばいいです。
    Taskを作るときに TaskCreationOptions.LongRunningを指定しなければ TheadPoolを使って実行されます。

    Task task = Task.Run(() => this.foobar(null))
    .ContinueWith((t) =>
    {
        //ContinueWithでつなげば、エラーが無かったか確認できる
        //必要ならここで外部にエラーを通知すればいいです
        if (t.Exception is System.AggregateException)
        {
            var aex=(System.AggregateException) t.Exception;
            System.Diagnostics.Debug.WriteLine(aex.InnerException.StackTrace);
        }
        else if (t.Exception != null)
        {
            System.Diagnostics.Debug.WriteLine(t.Exception.StackTrace);
        }
    });

    それとも、try~catchの形式でThreadPool内のエラーをキャッチしたいのですか?
    ならばasync/awaitでTaskを使えばできます。

    private async Task Test()
    {
        try
        {
            await Task.Run(() => this.foobar(null));
        }
        catch(Exception e)
        {
        }
    }

    どうしてもThreadPool.QueueUserWorkItemでやりたいなら、きちんと処理内でキャッチしましょう。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2019年10月31日 10:43
    • 回答としてマーク yj0529 2019年11月1日 1:39
    2019年10月31日 10:42
  • gekka様

    ご返答ありがとうございます。

    Taskを使わなければ例外を外側で取れないとのこと理解いたしました。

    後学のためにもしご存知でしたら教えていただきたいのですが、ThreadPool.QueueUserWorkItemで未補足例外があるとアプリケーションが落ちてしまう理由はなぜなんでしょうか。Taskは未補足例外があってもアプリケーションは落ちませんでしたのでその違いを知りたく思います。
    2019年11月1日 0:37
  • 結局のところ各スレッドにおいてコールスタックの最初まで例外がcatchされなければアプリケーションは終了されるという話で、Taskは大雑把には次のような処理をしているだけのことです。
    ThreadPool.QueueUserWorkItem(() => {
        try {
            action();
        }
        catch (Exception ex) {
            this.Exception = ex;
        }
    });
    なので、必ずしもTaskじゃなくてもかまいません。まあTask以外を使う理由も特にない場合がほとんどでしょうけども。
    • 回答としてマーク yj0529 2019年11月1日 1:39
    2019年11月1日 1:15
  • Hongliang 様

    ありがとうございます。

    Task内部でThreadPoolを使うときに、処理の例外を補足するようにしているから落ちないようになっているんですね。

    2019年11月1日 1:39
  • そもそも論として、例外を発生させるべきではありません。発生することが分かっている場合に、catchするのではなく、例外が発生しないようコードを修正するべきです。例外の推奨事項も確認してください。

    で、そうであるならば、例外は開発者の意図しない状態ですので、未処理の場合にプロセスを終了させる、というのが.NETの設計方針のようです。

    一方、TaskはthreadPoolとは意味合いが違います。Taskはデータありきの概念です。何等かの結果とそれを得るための処理という括りです。ですので、データを得る際に発生した例外はハンドリングされる、という設計です。

    • 回答としてマーク yj0529 2019年11月1日 5:21
    2019年11月1日 2:17
  • 佐祐理 様

    おっしゃるとおり例外を発生させないのが一番重要であることを踏まえた上で、万が一例外が起こってもアプリケーションを落とさないようにしたいと思っておりましたが、.NETの設計方針でしたらそれを考慮して対応しようと思います。

    Taskの概念についてもわかりやすかったです。ありがとうございます。

    2019年11月1日 5:21