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

时间:2010-02-05 11:59:22

标签: c# wpf multithreading

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

这是我目前的设置:

  • LinkedList<string>将照片存储到照片并导航到下一张照片
  • Dictionary<string, BitmapImage>存储预加载的图片

4 个答案:

答案 0 :(得分:2)

我会用这样的东西:

class ImageManager
{
  private Dictionary<string, Image> images=
    new Dictionary<string,Image>();

  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
}

答案 1 :(得分:0)

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

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

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

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

尼克

答案 2 :(得分:0)

马塞尔,

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

答案 3 :(得分:0)

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

public IEnumerable<BitmapImage> BitmapImages { get; private set }

private void PreloadImages(IEnumerbale<Uri> uriCollection)
{
    var bitmapImages= new List<BitmapImage>();

    uriCollection.ToObservable()
        .SelectMany(LoadImageAsync)
        .Catch(Observable.Empty<BitmapImage>())
        .Subscribe(bitmapImages.Add, 
        () =>
        {
            BitmapImages = bitmapImages;
        });
}

private IObservable<BitmapImage> LoadImageAsync(Uri uri)
{
    return Observable.CreateWithDisposable<BitmapImage>(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;
    });
}