2009-04-04 6 views
3

Я пытаюсь загрузить изображение в фоновом режиме, а затем обновить интерфейс. Я играю с этим весь день, и я не знаю, чего мне не хватает. Я получаю следующее сообщение об ошибке:Как загружать изображения в фоновом режиме?

"The calling thread cannot access this object because a different thread owns it."

Я охотился вокруг, следуя примеру после того, как, например, но я не могу найти ответ. Я также завернул код, касающийся пользовательского интерфейса в другом BeginInvoke.

Обновление 3: Мораль истории. ImageSource не является потокобезопасным для доступа.

Обновление 2: Это должно быть простым решением :). Я попробовал клонирование, но это не привело к успеху, но я получил другую ошибку: «Исключение было вызвано целью вызова».

Обновление 1: Я попробовал BackgroundWorker, но я все еще получаю ту же ошибку, но это происходит на brush.ImageSource.Height. Я правильно сигнализирую интерфейс? Какие-либо предложения?

Вот мой XAML:

<Window x:Class="Slideshow.Show" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> 
    <DockPanel> 
     <Canvas Background="Black" Name="canvas"> 
      <Viewbox Name="background" Stretch="Uniform"> 
       <Rectangle name="background" /> 
      </Viewbox> 
     </Canvas> 
    </DockPanel> 
</Window> 

Вот некоторые из кода позади:

namespace Slideshow 
{ 
    public class Show 
    { 
     public Show() 
     { 
      BackgroundWorker bw = new BackgroundWorker(); 
      bw.DoWork += new DoWorkEventHandler(bw_DoWork); 
      bw.RunWorkerCompleted += 
       new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted); 
      bw.RunWorkerAsync(); 
     } 

     void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
     { 
      BitmapSource bitmap = e.Result as BitmapSource; 

      if (bitmap != null) 
      { 
       this.Dispatcher.BeginInvoke(DispatcherPriority.Normal 
        (ThreadStart)delegate() 
       { 
        Image image = new Image(); 
        image.Source = bitmap; 
        background.Child = image; 
       }); 
      } 
     } 

     void bw_DoWork(object sender, DoWorkEventArgs e) 
     { 
      BitmapSource bitmapSource = CreateBrush(GetRandomFile()); 
      e.Result = bitmapSource; 
     } 
    } 
} 
+0

Попробуйте удалить часть Dispatcher.BeingInvoke. Весь смысл BackgroundWorker заключается в том, что событие автоматически переводится в поток пользовательского интерфейса для вас, поэтому вам не нужно беспокоиться. – Samuel

+0

Проблема заключалась в том, что ImageSource не гарантируется потокобезопасность, поэтому все умрет. Как только я заменил это с помощью BitmapSource, моя проблема была решена! – kevindaub

ответ

11

Что я хочу использовать ThreadPool.QueueUserWorkItem для загрузки изображения, а затем, когда операция завершена, я возвращаюсь к потоку пользовательского интерфейса с помощью потокового объекта Dispatcher. Источник изображения не является потокобезопасным, вам нужно будет использовать что-то вроде JpegBitmapDecoder, есть также PngBitmapDecoder.

Для примера:

public Window() 
{ 
    InitializeComponent(); 

    ThreadPool.QueueUserWorkItem(LoadImage, 
     "http://z.about.com/d/animatedtv/1/0/1/m/simp2006_HomerArmsCrossed_f.jpg"); 
} 

public void LoadImage(object uri) 
{ 
    var decoder = new JpegBitmapDecoder(new Uri(uri.ToString()), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); 
    decoder.Frames[0].Freeze(); 
    this.Dispatcher.Invoke(DispatcherPriority.Send, new Action<ImageSource>(SetImage), decoder.Frames[0]); 
} 

public void SetImage(ImageSource source) 
{ 
    this.BackgroundImage.Source = source; 
} 
+0

OMG! Я так расстроился с помощью ImageSource, которого я никогда не замечал в документации что он не был потокобезопасным. – kevindaub

+0

Там ya go. Точно так же, как и в том, как Forms/WPF является только STA, за исключением того, что он не документирован. – Samuel

1

Вы хотите включить несколько потоков в приложении WPF. Как указано в следующей статье, WPF заставляет вас выполнять всю работу пользовательского интерфейса в потоке, который создал пользовательский интерфейс.

Проверьте это: http://pjbelfield.wordpress.com/2007/10/29/worker-threads-and-the-dispatcher-in-wpf/

и для интенсивной работы пользовательского интерфейса:

http://eprystupa.wordpress.com/2008/07/28/running-wpf-application-with-multiple-ui-threads/

0

Ваш код для загрузки изображения (.jpg) в память должно быть сделано на заднем плане. Код для загрузки изображения из памяти в элемент управления WPF должен выполняться в обычном потоке WPF в пользовательском интерфейсе. В фоновый поток должны быть помещены только медленные/длительные задачи. Как только длинная задача будет завершена, она должна сигнализировать о потоке пользовательского интерфейса, чтобы обновить представление с результатом длинной задачи.

Мой любимый способ использует класс BackgroundWorker:

var bg = new System.ComponentModel.BackgroundWorker(); 
bg.DoWork += Work_Function; 
bg.RunWorkerCompleted += UI_Function; 
bg.RunWorkerAsync(); 

void Work_Function(object sender, DoWorkEventArgs e) { ... } 
void UI_Function(object sender, RunWorkerCompletedEventArgs e) { ... } 

При вызове RunWorkerAsync(), сначала ваш Work_Function() называется. Когда он будет завершен, вызывается UI_Function(). Функция работы может поместить одну переменную в свойство DoWorkEventArgs.Result, доступную из свойства RunWorkerCompletedEventArgs.Result.

Я думаю вас также будет заинтересовать этой статьей: http://www.codeproject.com/KB/WPF/WPF_Explorer_Tree.aspx Он показывает, как настроить Lazy Loading в древовидной структуре в WPF. Я думаю, что базовая концепция (добавление фиктивного узла, перехват отображения фиктивного узла путем загрузки следующего изображения вместо этого) будет применяться к вашей работе.

+0

Как подать сигнал на поток пользовательского интерфейса? – kevindaub

+0

Я обновил свой ответ, чтобы показать пример того, как это можно сделать. –

+0

Спасибо. Я сделаю это. – kevindaub

0

Ошибка, которую вы получаете сейчас, является хорошей новостью, это значит, что работает BackgroundWorker. Вы можете попробовать клонировать его в RunWorkerCompleted с brush.Clone();.

Попробуйте удалить Dispatcher.BeingInvoke. Весь смысл BackgroundWorker заключается в том, что событие автоматически переводится в поток пользовательского интерфейса для вас, поэтому вам не нужно беспокоиться.

+0

К сожалению, клонирование не сработало, но я получаю другую ошибку: «Исключение было выбрано целью вызова». – kevindaub

2

Еще одна вещь, которую мы имели в нашем проекте. Поскольку ImageSource помещается в пользовательский интерфейс, вам необходимо проверить, не заморожено ли оно:

public void SetImage(ImageSource source) 
{ 
    ImageSource src = null; 
    if(!source.IsFrozen) 
     src = source.GetAsFrozen(); 
    else 
     src = source; 
    this.BackgroundImage.Source = src; 
} 
+0

Я думаю, что у меня возникла проблема с тем, что вызов IsFrozen не был потокобезопасным. AKA if was wasn Я замолчал, я получил ошибку. Спасибо за головы. – kevindaub

+0

Я обновил метод SetImage, который используется в образце bendewey. Конечно, он должен быть вызван с Dispatcher.Invoke. И перед вызовом Dispatcher.Invoke вам лучше позвонить Dispatcher .CheckAccess, который вернет true, если отправка необходима и false, если вы можете обновить интерфейс без диспетчера – Methos

Смежные вопросы