• [WPF 容易忽视的细节] —— Exception in WPF's Converter


    前言:

    在WPF中,Converter是我们经常要用到的一个工具,因为XAML上绑定的数据不一定是我们需要的数据。

    问题:

    在Converter中抛出一个异常导致程序崩溃,而且是在对未捕获异常进行集中处理的情况。

    补充:错误场景。

    <Window x:Class="TestProject.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:TestProject"
            Title="MainWindow"
            Width="525"
            Height="350"
            DataContext="{Binding RelativeSource={RelativeSource Self}}">
        <Window.Resources>
            <local:ErrorConverter x:Key="ErrorConverter" />
        </Window.Resources>
        <Grid>
            <TextBlock Text="{Binding Content, Converter={StaticResource ErrorConverter}}" />
        </Grid>
    </Window>
    namespace TestProject
    {
        using System;
        using System.Globalization;
        using System.Windows.Data;
    
        /// <summary>
        /// 转换过程中跑出异常的Converter。
        /// </summary>
        public class ErrorConverter : IValueConverter
        {
            /// <summary>
            /// 只抛出错误,不做任何错误。
            /// </summary>
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new Exception("Just for test! - In ErrorConverter - Convert");
            }
    
            /// <summary>
            /// 只抛出错误,不做任何错误。
            /// </summary>
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new Exception("Just for test! - In ErrorConverter - ConvertBack");
            }
        }
    }

    上面的场景中,当TextBlock绑定到Content时,便会触发ErrorConverter的Convert方法,

    但是Convert方法因为抛出了异常,导致整个程序挂掉。

    虽然,在App.xaml.cs中集中对未捕获异常进行处理,但是却无法捕获这个异常。

    public App()
    {
      this.DispatcherUnhandledException +=new DispatcherUnhandledExceptionEventHandler(Application_DispatcherUnhandledException);
      AppDomain.CurrentDomain.UnhandledException +=new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
    }

    探索过程:

    1、UnhandledException

    因为程序中,难免会出现没有处理的异常,这些异常如果没有被处理就会先被抛给我们的主程序,

    如果还未被处理就会被抛给.Net Framework,最终导致程序挂掉。

    所以,我时常集中处理未被处理的这部分异常,以防止程序挂掉。

    相关内容可以参考:CSharp UnhandledException

    当捕获到未被处理的异常时,我就会弹出一个消息框,然后打印到日志中。

    理论上来说,这是可以捕获所有程序中抛出的异常的,但是当Converter中抛出异常时,却导致程序崩溃。

    通过Debug,导致崩溃异常为StackOverflowException。

    2、StackOverflowException

    一方面这个异常的名字,是我已经去的一个网站;另外一方面这个异常是无法被捕捉的。

    这是MSDN上的解释:StackOverflowException 。

    而且,我的经验是一般出现这个异常就是说明程序中出现了死循环。

    但是,当我检查程序时发现,我并没有写此类代码。

    而且错误出现在MessageBox.Show("");这行。(就是我在主程序中处理未捕获的异常时,抛出消息框)。

    // 当捕获未处理异常时
    Exception ex = e.Exception;
    const string errorMsg = "UI Thread Exception : 
    
    ";
    MessageBox.Show("An unhandled UI Thread exception occurred");
    Logger.Error(errorMsg + ex.Message + Environment.NewLine + ex.StackTrace);
    e.Handled = true;

    将此行注释掉,发现一切正常,程序不再挂掉。说明是MessageBox引起StackOverflowException。

    3、 MessageBox

    当MessageBox.Show("");时,会阻塞当前线程,所以理论上来说每次只能显示一个MessageBox。

    // 点击按钮执行以下代码
    // 只有点击MessageBox上的确认按钮才会显示下一个,不会一次全部显示。
    for (int i = 0; i < 5; i++)
    {
            MessageBox.Show("Just For test");
    }

    说明应该是MessageBox.Show被多次调用,造成StackOverflowException。

    4、 Logger

    再去检查日志,发现没有日志信息打印出来,说明应该是一直停留在MessageBox.Show这里,

    所以日志还没来得及打印程序便挂掉了。

    5、Trace Converter

    有一个猜想,就是Converter被调用了多次:当xaml解析器发现Converter抛出异常时,

    便再次调用Converter,导致又一次抛出异常,主程序捕获后,又执行MessageBox.Show,

    所以程序挂掉了。

    验证猜想:

    public class TestConverter : IValueConverter
        {
            // 临时变量,用于记录被调用的次数
            private int temp = 0;
    
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                // 打印出次数
                using (var writer = File.AppendText("D:\Log.txt"))
                {
                    writer.WriteLine(DateTime.Now + " - " + temp++);
                }
    
                throw new Exception("Just for test!");
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
    }    

    再次运行错误的程序,不例外程序崩溃,但是我也得到了想要的数据。

    9/12/2013 3:01:42 PM - 0
    9/12/2013 3:01:42 PM - 1
    9/12/2013 3:01:42 PM - 2
    9/12/2013 3:01:42 PM - 3
    9/12/2013 3:01:42 PM - 4
    9/12/2013 3:01:42 PM - 5
    9/12/2013 3:01:42 PM - 6
    9/12/2013 3:01:42 PM - 7
    9/12/2013 3:01:42 PM - 8
    9/12/2013 3:01:42 PM - 9
    9/12/2013 3:01:43 PM - 10
    9/12/2013 3:01:43 PM - 11
    9/12/2013 3:01:43 PM - 12
    9/12/2013 3:01:43 PM - 13
    9/12/2013 3:01:43 PM - 14

    跟我猜想的一样,但是Converter确被调用了15次,这也是导致程序崩溃的原因。

    结论:

    1、 当MessageBox.Show被同时多次调用时,会出现StackOverflowException的异常。

    这个我不知道如何验证,但是貌似是这样的。

    2、当xaml发现Converter抛出异常时,会继续执行Convert方法,最多15次。

    完全不知道这个有什么意义,已经抛出异常了,再次调用就不会有异常?

    3、尽量在Converter中将异常处理好,否则容易引起不必要的麻烦。

    如果条件错误、或者转换失败,则返回null。而不是抛出异常。

    写的时间比研究的时间长多了,希望对大家有帮助,希望大神能够回答我的疑问。

    引用请注明出处:Exception in WPF's Converter

  • 相关阅读:
    【Educational Codeforces Round 33 B】Beautiful Divisors
    【Educational Codeforces Round 33 A】Chess For Three
    【习题 6-7 UVA
    【习题 6-6 UVA
    【习题 6-5 UVA-1600】Patrol Robot
    【习题 6-4 UVA-439】Knight Moves
    【习题 6-3 UVA
    【POJ1144】Network(割点)(模板)
    【CF711D】Directed Roads(环,强连通分量)
    【CF711C】Coloring Trees(DP)
  • 原文地址:https://www.cnblogs.com/ColdJokeLife/p/3316989.html
Copyright © 2020-2023  润新知