【WPF】请注意一个 异步绑定 的陷阱!
WPF的异步绑定在很大程度上简化了延迟较大的绑定操作,大大提高了生产力。但在这个糖果里存在一个天大的陷阱。
如果你不能发现这个陷阱,轻则导致延迟严重,重则导致程序崩溃。
请看第一个案例:
<Image Source="http://images.cnblogs.com/adminlogo.gif" />
这段代码贴入VS2010以后,第一反应就是VS会假死一段时间。然后在Design窗口才会显示出园子的logo。运行段代码,程序也要假死一段时间才能正常工作。原因就是通过网络加载这个图片的时间过长。在我这需要10+秒。
看到这里,你一定会说,使用异步操作就可以了。
请看第二个案例:
前台代码:
<Image Source="{Binding IsAsync=True}"/>
后台代码:
public MainWindow()
{
InitializeComponent();
this.DataContext = "http://images.cnblogs.com/adminlogo.gif";
}
看上去这段代码能够完美的工作,可实际上效果和案例一完全一样。除了不会让VS假死一段时间以外。
原因是因为你给UI Thread传递的只是一个string,UI Thread调用Async Thread把它转化为Uri以后,继续用UI Thread去下载图片,自然还是会导致阻塞。
那么自然就会想起第三种方案。
请看第三个案例:
前台代码:
<Image Source="{Binding Image, IsAsync=True}"/>
后台代码:
public MainWindow()
{
InitializeComponent();
this.DataContext = new VM();
}
public class VM
{
public ImageSource Image
{
get
{
return new BitmapImage(new Uri("http://images.cnblogs.com/adminlogo.gif"));
}
}
}
这样的代码看上去绝对是完美了。
运行,并没有发现阻塞了UI Thread,正等待图片异步下载完毕的时候,突然弹出异常:
The calling thread cannot access this object because a different thread owns it.
在WPF中UI Thread是不能使用非UI Thread创建的Image的。
下面正式推出我们的终极解决方案:
前台代码不变,后台代码修改为
public MainWindow()
{
InitializeComponent();
this.DataContext = new VM(this.Dispatcher);
}
public class VM
{
Dispatcher _dispatcher = null;
public VM(Dispatcher dispatcher)
{
_dispatcher = dispatcher;
}
public ImageSource Image
{
get
{
if (_dispatcher == null)
return null;
BitmapImage bi = null;
try
{
WebClient wc = new WebClient();
MemoryStream ms = new MemoryStream(wc.DownloadData("http://images.cnblogs.com/adminlogo.gif"));
_dispatcher.Invoke(new Action(() =>
{
bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = ms;
bi.EndInit();
}), null);
}
catch { }
return bi;
}
}
}
现在你还会发现任何问题吗?