I've encountered a problem with displaying an image from the web in my WPF app:
I used a BitmapImage
for this task. My first attempt was to execute it in the UI thread but I quickly understood that's a no-no since the application became unresponsive until the image was completely loaded. My second attempt was to use a BackgroudWorker
:
var worker = new BackgroundWorker();
worker.DoWork += worker_LoadImage;
worker.RunWorkerCompleted+=worker_RunWorkerCompleted;
worker.RunWorkerAsync(someURI);
and the worker functions:
private void worker_LoadImage(object sender, DoWorkEventArgs e)
{
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnDemand;
image.UriSource = e.Argument as Uri;
image.DownloadFailed += new EventHandler<ExceptionEventArgs>(image_DownloadFailed);
image.EndInit();
e.Result = image;
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//if I understand correctly, this code runs in the UI thread so the
//access to the component image1 is valid.
image1.Source = e.Result as BitmapImage;
}
after that, I still got an InvalidOperationException: "The calling thread cannot access this object because a different thread owns it."
I've researched a bit and found out that since BitmapImage
is Freezable
, I have to call Freeze
before accessing the object from another thread.
So I've tried to replace the last row in worker_LoadImage with:
image.Freeze();
e.Result = image;
But then I got an exception that my image cannot be frozen, I found out that it's probably
because the image wasn't done being downloaded when I tried to invoke Freeze()
. So I added the following code to the image creation:
image.DownloadCompleted += image_DownloadCompleted;
where:
void image_DownloadCompleted(object sender, EventArgs e)
{
BitmapImage img = (BitmapImage)sender;
img.Freeze();
}
So now we get to the real question: How do I make the background worker to wait until the image is completely downloaded and the event is fired?
I've tried many things: looping while the image's isDownloading is true, Thread.Sleep, Thread.Yield, Semaphores, Event wait handles and more. I dont know how the image downloading actually works behind the scenes but what happens when I try one of the methods above is that the image never finishes to download (isDownloading is stuck on True)
Is there a better, simpler way to achieve the rather simple task im trying to accomplish?
Some things to notice:
this answer actually works, but only once: when I try to load another image it says the dispatcher is closed. Even after reading a bit about Dispatchers, I don't really understand how the OP achieved that or if it's possible to extend the solution for more than one image.
When I put a message box before the worker exits his DoWork function, I click OK and the image apears which means the download continued while the message box was opened and finished before I clicked OK.