如何在后台线程中预加载图像?

在我的WPF应用程序中,我需要加载一些图像。 我只需要一次显示一个图像。 如果我在需要时加载图像,则会稍有延迟。 所以我心想:“嘿,为什么不在后台线程中做一些预加载?不能那么难。” 我对线程有一些经验,但还不足以知道这个想法是错误的。 我开始编程并遇到一些问题。 我解决了一些问题,我可能也解决了其他问题,但这会产生意大利面条代码。 所以,我认为从头开始将是最好的。 需要什么初始规划来构建一个漂亮的小预加载线程? 是否有某种模式或类似的东西?

这是我目前的设置:

  • LinkedList用于存储图片的pathes并导航到下一张图片
  • Dictionary用于存储预加载的图像

我会用这样的东西:

 class ImageManager { private Dictionary images= new Dictionary(); public Image get(string s) { // blocking call, returns the image return load(s); } private Image load(string s) { // internal, thread-safe helper lock(images) { if(!images.ContainsKey(s)) { Image img=// load the image s images.Add(s,img); return img; } return images[s]; } } public void preload(params string[] imgs) { // non-blocking preloading call foreach(string img in imgs) { BackgroundWorker bw=new BackgroundWorker(); bw.DoWork+=(s,e)=>{ load(img); } // discard the actual image return bw.RunWorkerAsync(); } } } // in your main function { ImageManager im=new ImageManager(); im.preload("path1", "path2", "path3", "path4"); // non-blocking call // then you just request images based on their path // they'll become available as they are loaded // or if you request an image before it's queued to be loaded asynchronously // it will get loaded synchronously instead, thus with priority because it's needed } 

对于后台线程来说,这听起来确实很好用。 此外,由于您的工作单元非常大,因此对集合的同步不应有太多争用。 您可能会找到类似算法的示例,但我认为您必须推出自己的实现 – 它并不复杂。

但有一点需要注意:您要么必须记录当前正在加载的图像,要么承受同一图像的多个加载。

例如,如果您的UI需要尚未加载的图像,您可能希望将该图像作为优先级加载。 如果您知道后台线程正在加载该图像,您可以等待它变为可用。 如果您决定只在UI线程上执行加载,则后台线程可能会尝试添加已存在的已加载图像。

因此,必须有一些同步,但它不应该太复杂。

缺口

烫发,

WPF为我们提供了很好的BackgroundWorkerDispatcher机制,让您忘记编写自己的线程机制。 但是你的问题对我来说似乎并不那么明显。 您需要多少张图片/从哪里获得这些图片? 请给我们更多信息。

昨天我一直在寻找这个,但在这个问题上找不到多少。 实际上这个问题解决方案非常简单。 使用WebClient将图像异步加载到流中,然后将该流添加到BitmapImage。 Bellow是我如何实现预加载图像列表的一个例子。 该示例使用Reactive Extensions Library (Rx),但它可以很容易地以非Rx方式实现(Rx使代码更简洁并隐藏了很多状态)。

 public IEnumerable BitmapImages { get; private set } private void PreloadImages(IEnumerbale uriCollection) { var bitmapImages= new List(); uriCollection.ToObservable() .SelectMany(LoadImageAsync) .Catch(Observable.Empty()) .Subscribe(bitmapImages.Add, () => { BitmapImages = bitmapImages; }); } private IObservable LoadImageAsync(Uri uri) { return Observable.CreateWithDisposable(observer => { var downloader = new WebClient(); downloader.OpenReadCompleted += (s, e) => { if (e.Error != null) { observer.OnError(e.Error); } var bitmapImage = new BitmapImage(); bitmapImage.BeginInit(); bitmapImage.StreamSource = e.Result; bitmapImage.EndInit(); observer.OnNext(bitmapImage); observer.OnCompleted(); }; downloader.OpenReadAsync(uri); return downloader; }); }