Загрузка изображения отдельным потоком [C#] [WPF]

Discussion in 'С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby' started by Geass, 25 Aug 2012.

  1. Geass

    Geass New Member

    Joined:
    12 Apr 2012
    Messages:
    43
    Likes Received:
    2
    Reputations:
    0
    Здравствуйте. Прошу помощи. Попытался тут разобраться с TPL библиотекой, но возникает проблема с одной деталью...

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

    Класс изображения:

    Code:
    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;
            }
    
            /// <summary>
            /// Начинает загрузку изображения
            /// </summary>
            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();
                    }
                }
            }
        }
    } 
    Главное окно:

    Code:
     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 = new Task(SetPicture);
                tsk.Start();
            }
    
            public void SetPicture()
            {
                dp.Load();
                this.Dispatcher.BeginInvoke((ThreadStart)delegate() { this.viewImg.Source = dp.Picture; });
    
                Thread.Sleep(20);
            }
        }
    }
    
    XAML код:

    Code:
    <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>
     
     
    #1 Geass, 25 Aug 2012
    Last edited: 25 Aug 2012
  2. Spot

    Spot Elder - Старейшина

    Joined:
    1 Mar 2007
    Messages:
    462
    Likes Received:
    38
    Reputations:
    1
    Для начала Exception в студио!
    Task? Почему Task,а не Thread?
    Code:
     this.Dispatcher.BeginInvoke((ThreadStart)delegate(  ) { this.viewImg.Source = dp.Picture; });
    
    Что за масло маслянное?

    Советую ознакомиться
     
  3. VY_CMa

    VY_CMa Green member

    Joined:
    6 Jan 2012
    Messages:
    912
    Likes Received:
    474
    Reputations:
    723
    _________________________
  4. Geass

    Geass New Member

    Joined:
    12 Apr 2012
    Messages:
    43
    Likes Received:
    2
    Reputations:
    0
    Вот оно:
    Code:
    System.InvalidOperationException не обработано пользовательским кодом
      HResult=-2146233079
      Message=Вызывающий поток не может получить доступ к данному объекту, так как владельцем этого объекта является другой поток.
      Source=WindowsBase
      StackTrace:
           в System.Windows.Threading.Dispatcher.VerifyAccess()
           в System.Windows.Freezable.get_IsFrozen()
           в System.Windows.Controls.Image.UpdateBaseUri(DependencyObject d, ImageSource source)
           в System.Windows.Controls.Image.OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
           в System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
           в System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
           в System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
           в System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
           в System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType, Boolean isInternal)
           в System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)
           в System.Windows.Controls.Image.set_Source(ImageSource value)
           в JPGTest.MainWindow.<SetPicture>b__0() в c:\Users\Geass\Documents\Visual Studio 2012\Projects\JPGTest\JPGTest\MainWindow.xaml.cs:строка 28
      InnerException: 
    
    Task мне как то более удобным кажется.

    Вот чего не знаю, того не знаю.
     
  5. Spot

    Spot Elder - Старейшина

    Joined:
    1 Mar 2007
    Messages:
    462
    Likes Received:
    38
    Reputations:
    1
    кажется

    VY_CMa, привел верный пример - почитай. Если в кратце, то к UI елементам нельзя обращаться из второстепенных потоков, так как все гуй елементы находятся в главном. Для этого есть костыли - Backgroundworker

    P.S. смени язык, а то ссылка на немецкую версию.
     
  6. seosimf

    seosimf Member

    Joined:
    3 Mar 2011
    Messages:
    271
    Likes Received:
    44
    Reputations:
    6
    Они находятся в том потоке который их создал.
     
  7. Geass

    Geass New Member

    Joined:
    12 Apr 2012
    Messages:
    43
    Likes Received:
    2
    Reputations:
    0
    Тем не менее простейшие приложения прекрасно работают и обращаются к контролам из других потоков. Пример компилирующегося и работающего приложения:

    XAML:
    Code:
    <Window x:Class="Test.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>
            <TextBox x:Name="TextBox1" HorizontalAlignment="Left" Height="291" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="489"/>
        </Grid>
    </Window>
     
    Код главного окна программы:
    Code:
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows;
    
    namespace Test
    {
        /// <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(() =>
                    {
                        for (int i = 0; i < 100; i++)
                        {
                            Dispatcher.Invoke(() => TextBox1.Text += i + "   ");
                            Thread.Sleep(500);
                        }
                    });
            }
        }
    }
     
    Вполне неплохо создается еще одна задача, которая дописывает циферки в контрол TextBox, созданный в главном потоке.
     
  8. seosimf

    seosimf Member

    Joined:
    3 Mar 2011
    Messages:
    271
    Likes Received:
    44
    Reputations:
    6
    Потому что они используют синхронизацию для этого, все Invoke,BeginInvoke,Dispatcher фактически и есть синхронизация, использую их грубо говоря ты исполняешь код в контексте потока их создавшего.
    ПС и спасибо за пример я действительно не знал что так круто можно, вау, и на http://msdn.microsoft.com меня как и тебя тоже забанили.
     
  9. Geass

    Geass New Member

    Joined:
    12 Apr 2012
    Messages:
    43
    Likes Received:
    2
    Reputations:
    0
    А суть оказалась в том, что в классе DownloadPicture объект BitmapImage является Freezable объектом, и, соответственно, после его заполнения нужно было вызвать метод Freeze() для этого объекта. MSDN почему-то упустла из вида этот факт.

    Code:
     using (MemoryStream ms2 = new MemoryStream())
                    {
                        encoder.Save(ms2);
    
                        Picture = new BitmapImage();
    
                        Picture.BeginInit();
                        Picture.StreamSource = new MemoryStream(ms2.ToArray());
                        Picture.EndInit();
                    }
                    [B]Picture.Freeze();[/B]
     
     
    #9 Geass, 27 Aug 2012
    Last edited: 27 Aug 2012
Loading...