WPF UI 加载图像时的性能影响
WPF UI performance impact when loading images
我一直在研究如何最大限度地减少对 binding/loading 图像对列表的性能影响。
我尝试了一些人们在网上建议的不同选项,例如:
或使用单独的线程将图像列表加载到 ImageSource,然后绑定到列表视图
and/or 与标记为异步的主要源的优先绑定(当我单独使用这种方法时,我没有看到任何改进,tbh)
它们都有一定程度的改进,我最终得到了如下结果:
带有异步加载的自定义图像控件
public class AsyncImage : Image
{
private static ImageSource _blank;
private static Random random;
private static ImageSource Blank
{
get
{
if(_blank == null)
{
var bi = new BitmapImage();
bi.BeginInit();
Stream imgStream = File.OpenRead("D:\...\blankLoading.png");
bi.StreamSource = imgStream;
bi.EndInit();
bi.Freeze();
_blank = bi;
}
return _blank;
}
}
public string ImageUrl
{
get { return GetValue(ImageUrlProperty).ToString(); }
set
{
SetValue(ImageUrlProperty, value);
LoadImageAsync(value);
}
}
public static readonly DependencyProperty ImageUrlProperty =
DependencyProperty.Register("ImageUrl", typeof(string), typeof(AsyncImage), new UIPropertyMetadata(string.Empty, new PropertyChangedCallback(OnImageUrlChanged)));
private static void OnImageUrlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
AsyncImage control = (AsyncImage)d;
control.ImageUrl = e.NewValue.ToString();
}
private void LoadImageAsync(string url)
{
Image image = new Image();
base.Source = Blank;
ThreadPool.QueueUserWorkItem((r) =>
{
BitmapImage bi = null;
bi = new BitmapImage();
bi.BeginInit();
var bytes = File.ReadAllBytes(url); //i want to make sure data is loaded before assignment
bi.StreamSource = new MemoryStream(bytes);
bi.EndInit();
bi.Freeze(); //makes sure your image can be passed across threads
Console.Write(url);
image.Dispatcher.Invoke(DispatcherPriority.Normal,
(ThreadStart)delegate
{
base.Source = bi; //this is where it actually comes back to UI thread
});
});
}
}
使用这种方法,我只需将 URL 列表或文件路径绑定到列表视图,数据绑定将执行得更加顺畅......但是,它总是会导致 UI 当显示实际图像时(在最后一行)。当你有一个很好的最后图像列表时,冻结会更明显。
有办法解决这个问题吗?我想要的只是 UI 在图像显示时仍会响应...
您的代码有一些问题。您不得在 ImageUrl
属性 的 setter 中调用 LoadImageAsync()
。该方法必须改为在 OnImageUrlChanged
.
中调用
在 LoadImageAsync 中,您创建一个 Image 元素只是为了使用它的 Dispatcher,这完全没有意义。使用自定义控件的 Dispatcher,例如 this.Dispatcher
。还要写 this.Source
而不是 base.Source
.
您还必须关闭加载 BitmapImage 的流。设置 bi.CacheOption = BitmapCacheOption.OnLoad
并通过 using
块处理 MemoryStream。
就是说,考虑使用不带 ThreadPool.QueueUserWorkItem
和 Dispatcher.Invoke
的更现代的实现。
改用Task.Run
:
public class AsyncImage : Image
{
public static readonly DependencyProperty ImagePathProperty =
DependencyProperty.Register(
nameof(ImagePath), typeof(string), typeof(AsyncImage),
new PropertyMetadata(async (o, e) =>
await ((AsyncImage)o).LoadImageAsync((string)e.NewValue)));
public string ImagePath
{
get { return (string)GetValue(ImagePathProperty); }
set { SetValue(ImagePathProperty, value); }
}
private async Task LoadImageAsync(string imagePath)
{
Source = await Task.Run(() =>
{
using (var stream = File.OpenRead(imagePath))
{
var bi = new BitmapImage();
bi.BeginInit();
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.StreamSource = stream;
bi.EndInit();
bi.Freeze();
return bi;
}
});
}
}
我一直在研究如何最大限度地减少对 binding/loading 图像对列表的性能影响。
我尝试了一些人们在网上建议的不同选项,例如:
或使用单独的线程将图像列表加载到 ImageSource,然后绑定到列表视图
and/or 与标记为异步的主要源的优先绑定(当我单独使用这种方法时,我没有看到任何改进,tbh)
它们都有一定程度的改进,我最终得到了如下结果: 带有异步加载的自定义图像控件
public class AsyncImage : Image
{
private static ImageSource _blank;
private static Random random;
private static ImageSource Blank
{
get
{
if(_blank == null)
{
var bi = new BitmapImage();
bi.BeginInit();
Stream imgStream = File.OpenRead("D:\...\blankLoading.png");
bi.StreamSource = imgStream;
bi.EndInit();
bi.Freeze();
_blank = bi;
}
return _blank;
}
}
public string ImageUrl
{
get { return GetValue(ImageUrlProperty).ToString(); }
set
{
SetValue(ImageUrlProperty, value);
LoadImageAsync(value);
}
}
public static readonly DependencyProperty ImageUrlProperty =
DependencyProperty.Register("ImageUrl", typeof(string), typeof(AsyncImage), new UIPropertyMetadata(string.Empty, new PropertyChangedCallback(OnImageUrlChanged)));
private static void OnImageUrlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
AsyncImage control = (AsyncImage)d;
control.ImageUrl = e.NewValue.ToString();
}
private void LoadImageAsync(string url)
{
Image image = new Image();
base.Source = Blank;
ThreadPool.QueueUserWorkItem((r) =>
{
BitmapImage bi = null;
bi = new BitmapImage();
bi.BeginInit();
var bytes = File.ReadAllBytes(url); //i want to make sure data is loaded before assignment
bi.StreamSource = new MemoryStream(bytes);
bi.EndInit();
bi.Freeze(); //makes sure your image can be passed across threads
Console.Write(url);
image.Dispatcher.Invoke(DispatcherPriority.Normal,
(ThreadStart)delegate
{
base.Source = bi; //this is where it actually comes back to UI thread
});
});
}
}
使用这种方法,我只需将 URL 列表或文件路径绑定到列表视图,数据绑定将执行得更加顺畅......但是,它总是会导致 UI 当显示实际图像时(在最后一行)。当你有一个很好的最后图像列表时,冻结会更明显。
有办法解决这个问题吗?我想要的只是 UI 在图像显示时仍会响应...
您的代码有一些问题。您不得在 ImageUrl
属性 的 setter 中调用 LoadImageAsync()
。该方法必须改为在 OnImageUrlChanged
.
在 LoadImageAsync 中,您创建一个 Image 元素只是为了使用它的 Dispatcher,这完全没有意义。使用自定义控件的 Dispatcher,例如 this.Dispatcher
。还要写 this.Source
而不是 base.Source
.
您还必须关闭加载 BitmapImage 的流。设置 bi.CacheOption = BitmapCacheOption.OnLoad
并通过 using
块处理 MemoryStream。
就是说,考虑使用不带 ThreadPool.QueueUserWorkItem
和 Dispatcher.Invoke
的更现代的实现。
改用Task.Run
:
public class AsyncImage : Image
{
public static readonly DependencyProperty ImagePathProperty =
DependencyProperty.Register(
nameof(ImagePath), typeof(string), typeof(AsyncImage),
new PropertyMetadata(async (o, e) =>
await ((AsyncImage)o).LoadImageAsync((string)e.NewValue)));
public string ImagePath
{
get { return (string)GetValue(ImagePathProperty); }
set { SetValue(ImagePathProperty, value); }
}
private async Task LoadImageAsync(string imagePath)
{
Source = await Task.Run(() =>
{
using (var stream = File.OpenRead(imagePath))
{
var bi = new BitmapImage();
bi.BeginInit();
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.StreamSource = stream;
bi.EndInit();
bi.Freeze();
return bi;
}
});
}
}