Question

In my current project, I need to download the file from the Internet once and use them in my WPF application later. I've set up this lazy loading pattern in order to download/initialize BitmapImage only when requested by application, but despite my utmost effort, I can't get it to work. The problem is that I get IOException when it tries to instantiate BitmapImage.

Below I post a complete application, that you can run yourself and arrive the same problem...

public partial class MainWindow : Window, INotifyPropertyChanged
    {
        string url = @"http://upload.wikimedia.org/wikipedia/commons/d/d3/Nelumno_nucifera_open_flower_-_botanic_garden_adelaide2.jpg";
        string filename = "PrettyFlower.jpg";
        Mutex m = new Mutex();
        bool isLoaded = false;
        BitmapImage flowerImage = null;

        public MainWindow()
        {
            InitializeComponent();

            Binding b = new Binding("FlowerImage");
            b.Source = this;
            img.SetBinding(Image.SourceProperty, b);
        }

        public BitmapImage FlowerImage 
        {
            get
            {
                if (isLoaded == false)
                {
                    LoadImage();
                    return null;
                }
                return flowerImage;
            }
        }

        private async void LoadImage()
        {
            WebClient wc = new WebClient();
            m.WaitOne();

            FileInfo fi = new FileInfo(filename);
            if (fi.Exists == false)
            {
                await wc.DownloadFileTaskAsync(url, filename);
            }

            flowerImage = new BitmapImage(new Uri("pack://siteOfOrigin:,,,/" + filename));
            isLoaded = true;
            OnPropertyChanged("FlowerImage");

            m.ReleaseMutex();
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

And corresponding MainWindow.xaml is nothing more but:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
   <Image x:Name="img"/>
</Window>

I do notice whats going on, but honestly I don't understand why is this happening - that is, BitmapImage is instantiated before the download completes, but shouldn't mutex here prevent this behaviour?

Was it helpful?

Solution 2

Ok, I got this to work finally, and it is not like Clemens said.. :(

The problem seems to be ... (and I believe it is) that LoadImage doesn't start another thread until it calls wc.DownloadFileTaskAsync. This is the only thing that runs in another thread, therefore... the call to WaitOne() is completly silly, since mutex is claimed by UI thread ...

The way I got it to work is...

private async void LoadImage()
        {
            WebClient wc = new WebClient();

            await Task.Run(() =>
                {
                    m.WaitOne();

                    FileInfo fi = new FileInfo(filename);
                    if (fi.Exists == false)
                    {
                        wc.DownloadFile(url, filename);
                        wc.Dispose();
                    }

                    m.ReleaseMutex();
                });

            m.WaitOne();
            flowerImage = new BitmapImage(new Uri("pack://siteOfOrigin:,,,/" + filename));
            m.ReleaseMutex();

            isLoaded = true;
            OnPropertyChanged("FlowerImage");
        }

OTHER TIPS

You won't use a Pack URI for this. Instead use a plain file URI that references the downloaded file (where filename is the full path as in your code):

flowerImage = new BitmapImage(new Uri(filename));
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top