踩坑日记-wpf多值转换器错用导致ui阻塞

  • ~4.42K 字

也是个很奇葩的问题了,有一个多值转换器IMultiValueConverter在ItemsControl里当成IValueConverter使用,编译不报错,可正常运行,但就是会阻塞ui。代码如下

xaml

1
2
3
4
5
6
7
8
9
10
11
12
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border>
<Grid>
<TextBox
Text="{Binding Rate, Converter={StaticResource DataTransferRateUnitValuesConverter}}" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

csharp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
public class DataTransferRateUnitValuesConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (parameter != null && parameter.ToString() == "bps" && values.Length == 1 && values[0] is double rate1)
{
return $"{DataUnitHelper.GetTransferRate(rate1)}";
}
if (values.Length == 2 && values[0] is string channelname && values[1] is double rate)
{
if (channelname.StartsWith("pps", StringComparison.CurrentCultureIgnoreCase))
{
return $"{rate:0.###} pps";
}
else
{
return $"{DataUnitHelper.GetTransferRate(rate)}";
}
}

return "0 bps";
}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

public class DataUnitHelper
{
private static readonly ulong TbLength = Convert.ToUInt64(Math.Pow(1024, 4));
private static readonly ulong GbLength = Convert.ToUInt64(Math.Pow(1024, 3));
private static readonly ulong MbLength = Convert.ToUInt64(Math.Pow(1024, 2));
private static readonly ulong KbLength = 1024;

private static readonly ulong TbpsLength = Convert.ToUInt64(Math.Pow(10, 12));
private static readonly ulong GbpsLength = Convert.ToUInt64(Math.Pow(10, 9));
private static readonly ulong MbpsLength = Convert.ToUInt64(Math.Pow(10, 6));
private static readonly ulong KbpsLength = Convert.ToUInt64(Math.Pow(10, 3));

public static string GetLength(ulong length)
{
if (length >= TbLength)
{
return (length * 1.0 / TbLength).ToString("0.###") + " TB";
}

if (length >= GbLength)
{
return (length * 1.0 / (GbLength)).ToString("0.###") + " GB";
}

if (length >= MbLength)
{
return (length * 1.0 / (MbLength)).ToString("0.###") + " MB";
}

if (length >= KbLength)
{
return (length * 1.0 / KbLength).ToString("0.###") + " KB";
}

return length + " B";
}

public static string GetTransferRate(double rate)
{
if (rate >= TbpsLength)
{
return (rate * 1.0 / TbpsLength).ToString("0.###") + " Tbps";
}

if (rate >= GbpsLength)
{
return (rate * 1.0 / (GbpsLength)).ToString("0.###") + " Gbps";
}

if (rate >= MbpsLength)
{
return (rate * 1.0 / (MbpsLength)).ToString("0.###") + " Mbps";
}

if (rate >= KbpsLength)
{
return (rate * 1.0 / KbpsLength).ToString("0.###") + " Kbps";
}

return rate.ToString("0.###") + " bps";
}
}

经查阅资料,原来wpf做了自动兼容容错:把IMultiValueConverter当成 IValueConverter用,wpf不会崩溃,只会默默返回 DependencyProperty.UnsetValue

故而ui阻塞过程:

用错转换器 → WPF 引擎进入无限兼容 / 重试逻辑 → ui线程死循环阻塞

当然,改过来就行了。

我是因为这个转换器原本是IValueConverter,由于需求变化,改成了IMultiValueConverter,但妙就秒在这个转换器很多地方在用,因为不会显式的提醒编译报错,故而遗漏这个,这里也是很奇怪,其他的地方没有ItemsControl包裹确实是会提醒报错地方的,悲啊,最后定位到这个转换器全局查找才找到的。