none
Многопоточная загрузка-отображение картинки RRS feed

  • Вопрос

  • Здравствуйте. Прошу помощи. Попытался тут разобраться с TPL библиотекой, но возникает проблема с одной деталью...

    Суть тестового приложения: создаю форму, после формы запускаю второй поток, который скачивает картинку, а по завершении скачивания - отобразит ее в Image, который находится на главной форме. Но что-то оно не хочет работать как надо, вываливаясь с исключением, что что-то там из одного потока не принадлежит второму.

    В общем исходники:

    Класс  картинки:

    using System;
    using System.IO;
    using System.Net;
    using System.Threading;
    using System.Windows.Media.Imaging;
    
    namespace JPGTest
    {
        public class DownloadPicture
        {
            public BitmapImage Picture { get; set; }
            private string Url { get; set; }
    
            public DownloadPicture(string _url)
            {
                Url = _url;
            }
    
            public void Load()
            {
                WebRequest request = WebRequest.Create(Url);
                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                Stream pic = response.GetResponseStream();
    
                byte[] img;
    
                using (MemoryStream ms = new MemoryStream())
                {
                    short tmp = (short)pic.ReadByte();
                    while (tmp != -1)
                    {
                        ms.WriteByte((byte)tmp);
                        tmp = (short)pic.ReadByte();
                    }
                    img = ms.ToArray();
                }
    
                using (MemoryStream ms = new MemoryStream(img))
                {
                    JpegBitmapDecoder decoder = new JpegBitmapDecoder(ms, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
                    BitmapSource bmpSource = decoder.Frames[0];
    
                    JpegBitmapEncoder encoder = new JpegBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(bmpSource));
    
                    using (MemoryStream ms2 = new MemoryStream())
                    {
                        encoder.Save(ms2);
    
                        Picture = new BitmapImage();
    
                        Picture.BeginInit();
                        Picture.StreamSource = new MemoryStream(ms2.ToArray());
                        Picture.EndInit();
                    }
                }
            }
        }
    }
    

    Главное окно программы:

    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows;
    
    namespace JPGTest
    {
        /// <summary>
        /// Логика взаимодействия для MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            public DownloadPicture dp { get; set; }
            public MainWindow()
            {
                InitializeComponent();
                dp = new DownloadPicture(@"http://i43.fastpic.ru/big/2012/0824/6d/3f13eaad98a8800fe71cc180915b4c6d.jpg");
            }
    
            private void Window_Loaded_1(object sender, RoutedEventArgs e)
            {
                Task tsk = Task.Factory.StartNew(SetPicture);
            }
    
            public void SetPicture()
            {
                dp.Load();
                this.Dispatcher.BeginInvoke((ThreadStart)delegate()
                {
                    this.viewImg.Source = dp.Picture;
                });
                Thread.Sleep(20);
            }
        }
    }
    

    XAML код:

    <Window x:Class="JPGTest.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" Loaded="Window_Loaded_1">
        <Grid>
            <Image HorizontalAlignment="Left" Height="291" Margin="10,10,0,0" VerticalAlignment="Top" Width="489" x:Name="viewImg"/>
    
        </Grid>
    </Window>
    

    Заранее спасибо за ответы.

    DreamSpark Premium User

    24 августа 2012 г. 10:50

Ответы

  • Привет.

    У вас проблема не в построении потоков и обновлении UI, а в том что BitmapImage является Freezable объектом и он захвачен потоком, который его создал, а это не UI поток и поэтому у вас возникает исключение о доступе из другого потока. Чтобы UI поток мог получить доступ к BitmapImage, нужно после его заполнения вызвать метод Freeze(), чтобы "заморозить" объект, т.е. сделать не изменяемым.

    //ваш код...
     using (MemoryStream ms2 = new MemoryStream())
                    {
                        encoder.Save(ms2);
    
                        Picture = new BitmapImage();
    
                        Picture.BeginInit();
                        Picture.StreamSource = new MemoryStream(ms2.ToArray());
                        Picture.EndInit();
                    }
    
                    Picture.Freeze();

    А еще я бы немного изменил вывоз метода SetPicture, точнее убрал бы его совсем:
     dp = new DownloadPicture(@"http://i43.fastpic.ru/big/2012/0824/6d/3f13eaad98a8800fe71cc180915b4c6d.jpg");
                Task tsk = Task.Factory.StartNew(dp.Load).ContinueWith((task) =>
                {
                    this.viewImg.Source = dp.Picture;
                }, TaskScheduler.FromCurrentSynchronizationContext());


    Для связи [mail]

    • Помечено в качестве ответа asdfxcbneftyherwe 27 августа 2012 г. 14:45
    27 августа 2012 г. 8:21

Все ответы

  • Совсем никто не знает? :-(


    DreamSpark Premium User

    25 августа 2012 г. 9:08
  • на выходных мало отвечают

    честно говоря я не очень понял код и в частности SetPicture().
    просто мне показалось, что тут Load вызывается, и сразу картинку к нему цепляют, которая ещё не загружена,
    если она грузится в другом потоке.
    А если и загружена, то она, наверное, принадлежит другому потоку.

    Может быть мои непонятки вызваны не знанием WPF

    • Изменено INFEL8 25 августа 2012 г. 10:47
    25 августа 2012 г. 10:46
  • Почти правильно всё, но только надо учесть что dp тоже принадлежит классу MainWindow и надо его вызвать в Dispatcher

    public void SetPicture()
        {
          Dispatcher.BeginInvoke(DispatcherPriority.Normal, (ThreadStart)delegate(){
            dp.Load();
            this.viewImg.Source = dp.Picture; });
        }

    Так работать будет.

    26 августа 2012 г. 12:50
    Модератор
  • Да, так работает, но из-за того, что dp.Load() выполняется внутри Invoke(), то теряется смысл отдельного потока, так как фактически, он выполняется на главной форме, подвешивая UI. :-(

    ПС: а в чем разница между Invoke() И BeginInvoke(), если говорить простым языком?

    ППС: немного переделал код главного окна приложения, места поменьше стал занимать, но все равно не работает :-(

    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows;
    using System;
    using System.Windows.Media.Imaging;
    
    namespace JPGTest
    {
    
        /// <summary>
        /// Логика взаимодействия для MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
    
            private void Window_Loaded_1(object sender, RoutedEventArgs e)
            {
                Task tsk = Task.Factory.StartNew(() =>
                    {
                        DownloadPicture dp = new DownloadPicture(@"http://i43.fastpic.ru/big/2012/0824/6d/3f13eaad98a8800fe71cc180915b4c6d.jpg");
                        dp.Load();
                        Dispatcher.Invoke(() => viewImg.Source = dp.Picture);
                        Thread.Sleep(TimeSpan.FromSeconds(5));
                    });
            }
        }
    }


    DreamSpark Premium User





    26 августа 2012 г. 18:24
  • "а в чем разница между Invoke() И BeginInvoke(), если говорить простым языком?" - первый работает синхронно, второй асинхронно. В этом случае нужно использовать BackgroundWorker.
    26 августа 2012 г. 19:25
    Модератор
  • С Backgroundworker сейчас попробую разобраться, но что делать в случае, если поток, который должен получать доступ к UI не один? Backgroundworker, как понимаю, только один может быть?

    DreamSpark Premium User

    26 августа 2012 г. 19:34
  • Привет.

    У вас проблема не в построении потоков и обновлении UI, а в том что BitmapImage является Freezable объектом и он захвачен потоком, который его создал, а это не UI поток и поэтому у вас возникает исключение о доступе из другого потока. Чтобы UI поток мог получить доступ к BitmapImage, нужно после его заполнения вызвать метод Freeze(), чтобы "заморозить" объект, т.е. сделать не изменяемым.

    //ваш код...
     using (MemoryStream ms2 = new MemoryStream())
                    {
                        encoder.Save(ms2);
    
                        Picture = new BitmapImage();
    
                        Picture.BeginInit();
                        Picture.StreamSource = new MemoryStream(ms2.ToArray());
                        Picture.EndInit();
                    }
    
                    Picture.Freeze();

    А еще я бы немного изменил вывоз метода SetPicture, точнее убрал бы его совсем:
     dp = new DownloadPicture(@"http://i43.fastpic.ru/big/2012/0824/6d/3f13eaad98a8800fe71cc180915b4c6d.jpg");
                Task tsk = Task.Factory.StartNew(dp.Load).ContinueWith((task) =>
                {
                    this.viewImg.Source = dp.Picture;
                }, TaskScheduler.FromCurrentSynchronizationContext());


    Для связи [mail]

    • Помечено в качестве ответа asdfxcbneftyherwe 27 августа 2012 г. 14:45
    27 августа 2012 г. 8:21
  • Вааай, спасибо огромное! ^__^ Теперь работает как надо.

    DreamSpark Premium User

    27 августа 2012 г. 14:12