2011-03-25 10 views
18

Bu site ve diğer forumlarda zaten bir sürü soru var, ancak aslında işe yarayan bir çözüm buldum.WPF içinde arka plan parçacığındaki bir resmi yükleme

İşte yapmak istediğim şey bu:

  • benim WPF uygulamasında, ben bir resim yüklemek istiyorum.
  • Görüntü, web üzerindeki rasgele bir URI'den alınmıştır.
  • Görüntü herhangi bir biçimde olabilir.
  • Aynı resmi birden çok kez yüklerseniz, standart Windows Internet önbelleğini kullanmak istiyorum.
  • Resim yükleme ve kod çözme, eşzamanlı olarak yapılmalıdır, ancak UI İş Parçasında değil.
  • Sonunda, bir <Image> adlı kullanıcının kaynak özelliğine uygulayabileceğim bir şeyle bitirmeliyim.

şeyler denedim:

  • bir BackgroundWorker üzerinde WebClient.OpenRead() kullanma. İyi çalışır, ancak önbelleği kullanmaz. WebClient.CachePolicy, yalnızca söz konusu WebClient örneğini etkiler.
  • WebRequest'i WebClient yerine Backgroundworker'da kullanma ve WebRequest.DefaultCachePolicy'yi ayarlama. Bu önbellek düzgün kullanır, ancak bana yarım saatte bozulmamış görüntüler vermeyen bir örnek görmedim.
  • Bir BackgroundWorker'da BitmapImage oluşturma, BitmapImage.UriSource öğesini ayarlama ve BitmapImage.DownloadCompleted işlemek için çalışıyor. Bu, BitmapImage.CacheOption ayarlanmışsa önbellek kullanır gibi görünüyor, ancak BackgroundWorker hemen döndürdüğü için DownloadCompleted işlemek için uzak görünüyor.

Ben tam anlamıyla bu aylarca mücadele ediyorum ve imkansız olduğunu düşünmeye başladım, ama muhtemelen benden daha akıllısın. Ne düşünüyorsun?

+0

Eğer BackgroundWorker kodunu gönderebilir miyim? – Rusty

cevap

13

Bu sorunu WebClient ve BitmapImage ile birlikte de dahil olmak üzere çeşitli şekillerde ele aldım.

DÜZENLEME: Orijinal öneri BitmapImage(Uri, RequestCachePolicy) yapıcı kullanmaktı, ama bu yöntem sadece yerel dosyalar, web değil kullanıyordum test projemi fark etti. Test edilen diğer web tekniğimi kullanmak için rehberlik değiştirme.

Sen görüntü, görüntü şifresini çözmek için gerekli küçük ama önemli zaman vardır senkron olsun veya indirildikten sonra, çünkü yükleme sırasında bir arka plan iş parçacığı üzerinde yükleme ve çözme çalışmalıdır. Çok sayıda görüntü yüklüyorsanız, bu, UI iş parçacığının duraklamasına neden olabilir. (Burada DelayCreation gibi birkaç başka karışıklık var ama sizin sorunuza uymuyorlar.)

Görüntü yüklemek için birkaç yol var, ancak bir BackgroundWorker'da web'den yükleme yapmak için buldum. WebClient veya benzer bir sınıf kullanarak verileri kendiniz indirmeniz gerekecek.

BitmapImage'ın bir WebClient'i dahili olarak kullandığını ve ayrıca çok sayıda hata işleme ve kimlik bilgileri ile farklı durumlar için anlatabilmemiz gereken diğer öğelerin bulunduğunu unutmayın. Bu parçacığı sağlıyorum, ancak yalnızca sınırlı sayıda durumda test edildi. Proxy'lerle, kimlik bilgileriyle veya başka senaryolarla uğraşıyorsanız, bu biraz masaj yapmalısınız.

BackgroundWorker worker = new BackgroundWorker(); 
worker.DoWork += (s, e) => 
{ 
    Uri uri = e.Argument as Uri; 

    using (WebClient webClient = new WebClient()) 
    { 
     webClient.Proxy = null; //avoids dynamic proxy discovery delay 
     webClient.CachePolicy = new RequestCachePolicy(RequestCacheLevel.Default); 
     try 
     { 
      byte[] imageBytes = null; 

      imageBytes = webClient.DownloadData(uri); 

      if (imageBytes == null) 
      { 
       e.Result = null; 
       return; 
      } 
      MemoryStream imageStream = new MemoryStream(imageBytes); 
      BitmapImage image = new BitmapImage(); 

      image.BeginInit(); 
      image.StreamSource = imageStream; 
      image.CacheOption = BitmapCacheOption.OnLoad; 
      image.EndInit(); 

      image.Freeze(); 
      imageStream.Close(); 

      e.Result = image; 
     } 
     catch (WebException ex) 
     { 
      //do something to report the exception 
      e.Result = ex; 
     } 
    } 
}; 

worker.RunWorkerCompleted += (s, e) => 
    { 
     BitmapImage bitmapImage = e.Result as BitmapImage; 
     if (bitmapImage != null) 
     { 
      myImage.Source = bitmapImage; 
     } 
     worker.Dispose(); 
    }; 

worker.RunWorkerAsync(imageUri); 

Bunu basit bir projede denedim ve iyi çalışıyor. Önbelleğe çıkıp çıkmadığı konusunda% 100 değilim, ancak MSDN'den anlatabildiğim, diğer forum soruları ve PresentationCore'a yansıtıcı olarak önbellek vurmalıyım. WebClient, HTTPWebRequest'i saran WebRequest'i ve bu şekilde devam eder ve önbellek ayarları her katmandan geçirilir.

BitmapImage BeginInit/EndInit çifti, gereksinim duyduğunuz ayarları aynı anda ve ardından EndInit sırasında yürütmeyi sağlar. Başka bir özellik belirlemeniz gerekirse, boş kurucuyu kullanmalı ve EndInit'i aramadan önce ihtiyacınız olanı ayarlayarak yukarıdaki gibi BeginInit/EndInit çiftini yazmalısınız.

Genellikle de EndInit sırasında belleğe resim yüklemek için zorlar, bu seçeneği, set

:

image.CacheOption = BitmapCacheOption.OnLoad; 

Bu daha iyi çalışma zamanı performansı için mümkün yüksek bellek kullanımını kapalı ticaret yapacak. Bunu yaparsanız, BitmapImage bir URL'den zaman uyumsuzluğu gerektirmedikçe BitmapImage, EndInit içinde eşzamanlı olarak yüklenir.

Daha notlar: UriSource mutlak Uri ve bir http veya https düzeni ise

BitmapImage zaman uyumsuz indirecektir. Endinit'ten sonra BitmapImage.IsDownloading özelliğini kontrol ederek indirip indirmediğini anlayabilirsiniz. DownloadCompleted, DownloadFailed ve DownloadProgress olayları vardır, ancak arka plan iş parçacığı üzerinde ateş etmek için ekstra zor olması gerekir. BitmapImage sadece eşzamansız bir yaklaşım sergilediğinden, indirme işlemi tamamlanana kadar iş parçacığını canlı tutmak için WPF eşdeğeri DoEvents() ile bir while döngüsü eklemeniz gerekir. Bu snippet'indeki çalışır DoEvents için This thread gösterileri kodu: Yukarıdaki yaklaşım çalışsa da

worker.DoWork += (s, e) => 
    { 
     Uri uri = e.Argument as Uri; 
     BitmapImage image = new BitmapImage(); 

     image.BeginInit(); 
     image.UriSource = uri; 
     image.CacheOption = BitmapCacheOption.OnLoad; 
     image.UriCachePolicy = new RequestCachePolicy(RequestCacheLevel.Default); 
     image.EndInit(); 

     while (image.IsDownloading) 
     { 
      DoEvents(); //Method from thread linked above 
     } 
     image.Freeze(); 
     e.Result = image; 
    }; 

bunun nedeni DoEvents() bir kod kokusu vardır, ve bunu WebClient, proxy ya da başka şeyler yapılandırmak izin etmediğini daha iyi performansla yardımcı olabilir. Yukarıdaki ilk örnek bu konuda önerilmektedir.

+0

"Gerçekten bir BackgroundWorker kullanmak istiyorsanız, bunu yapabilirsiniz, ancak görüntüyü indirilene kadar çalışan iş parçacığının geri dönmesini geciktirmeniz ve geri aramayı göndermeniz gerekebilir." Bunu yapmaya çalışıyorum ama hiç işe yaramıyor. İndirme doğrudur, ancak DownloadCompleted olayı hiç tetiklemez. –

+0

@ H.B. Bir örnek eklemek için düzenleyeceğim. –

+0

@ H.B. Bir örnek ekledim ve ayrıca daha fazla test ve araştırmayı yansıtmak için cevabımı yeniden düzenledim. –

8

BitmapImage, tüm olayları ve iç öğeleri için uyumsuz desteğe gereksinim duyar. Arka plan iş parçacığı üzerinde Dispatcher.Run() çağrı ... iş parçacığı için dağıtıcıyı iyi çalıştırır. (BitmapImage DispatcherObject öğesinden devralır, dolayısıyla bir dağıtıcıya ihtiyaç duyar. BitmapImage'ı oluşturan iş parçacığı zaten bir dağıtıcıya sahip değilse, istek üzerine yeni bir tane oluşturulacaktır.).

Önemli güvenlik ipucu: BitmapImage, önbellekten (sıçanlar) veri çekiyorsa hiçbir olay YETMEZ.

Bu .... benim için çok iyi çalışmaktadır

 var worker = new BackgroundWorker() { WorkerReportsProgress = true }; 

    // DoWork runs on a brackground thread...no thouchy uiy. 
    worker.DoWork += (sender, args) => 
    { 
     var uri = args.Argument as Uri; 
     var image = new BitmapImage(); 

     image.BeginInit(); 
     image.DownloadProgress += (s, e) => worker.ReportProgress(e.Progress); 
     image.DownloadFailed += (s, e) => Dispatcher.CurrentDispatcher.InvokeShutdown(); 
     image.DecodeFailed += (s, e) => Dispatcher.CurrentDispatcher.InvokeShutdown(); 
     image.DownloadCompleted += (s, e) => 
     { 
      image.Freeze(); 
      args.Result = image; 
      Dispatcher.CurrentDispatcher.InvokeShutdown(); 
     }; 
     image.UriSource = uri; 
     image.EndInit(); 

     // !!! if IsDownloading == false the image is cached and NO events will fire !!! 

     if (image.IsDownloading == false) 
     { 
      image.Freeze(); 
      args.Result = image; 
     } 
     else 
     { 
      // block until InvokeShutdown() is called. 
      Dispatcher.Run(); 
     } 
    }; 

    // ProgressChanged runs on the UI thread 
    worker.ProgressChanged += (s, args) => progressBar.Value = args.ProgressPercentage; 

    // RunWorkerCompleted runs on the UI thread 
    worker.RunWorkerCompleted += (s, args) => 
    { 
     if (args.Error == null) 
     { 
      uiImage.Source = args.Result as BitmapImage; 
     } 
    }; 

    var imageUri = new Uri(@"http://farm6.static.flickr.com/5204/5275574073_1c5b004117_b.jpg"); 

    worker.RunWorkerAsync(imageUri);