2009-12-17 2 views
26

У меня есть два класса ViewModel: PersonViewModel и PersonSearchListViewModel. Одним из полей PersonViewModel реализуется образ профиля, который загружается через WCF (кэшируется локально в изолированном хранилище). PersonSearchListViewModel - это контейнерный класс, в котором содержится список лиц. Поскольку загрузка изображений относительно тяжелая, PersonSearchListViewModel загружает только изображения для текущей, следующей и предыдущей страницы (результаты отображаются в пользовательском интерфейсе) ... для дальнейшего улучшения загрузки изображений я помещаю загрузку изображений в другой поток. Однако многопоточный подход вызывает проблемы с доступом к нескольким потокам.Недопустимая проблема доступа к сквозным потокам

PersonViewModel:

public void RetrieveProfileImage() 
{ 
    Image profileImage = MemorialDataModel.GetImagePerPerson(Person); 
    if (profileImage != null) 
    { 
     MemorialDataModel.ImageManager imgManager = new MemorialDataModel.ImageManager(); 
     imgManager.GetBitmap(profileImage, LoadProfileBitmap); 
    } 
} 

private void LoadProfileBitmap(BitmapImage bi) 
{ 
    ProfileImage = bi; 
    // update 
    IsProfileImageLoaded = true; 
} 

private BitmapImage profileImage; 
public BitmapImage ProfileImage 
{ 
    get 
    { 
     return profileImage; 
    } 
    set 
    { 
     profileImage = value; 
     RaisePropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("ProfileImage")); 
    } 
} 

PersonSearchListViewModel:

private void LoadImages() 
{ 
    // load new images 
    Thread loadImagesThread = new Thread(new ThreadStart(LoadImagesProcess)); 
    loadImagesThread.Start(); 

    //LoadImagesProcess(); If executed on the same thread everything works fine 
} 

private void LoadImagesProcess() 
{ 
    int skipRecords = (PageIndex * PageSize); 
    int returnRecords; 

    if (skipRecords != 0) 
    { 
     returnRecords = 3 * PageSize; // page before, cur page and next page 
    } 
    else 
    { 
     returnRecords = 2 * PageSize; // cur page and next page 
    } 

    var persons = this.persons.Skip(skipRecords).Take(returnRecords); 

    // load images 
    foreach (PersonViewModel pvm in persons) 
    { 
     if (!pvm.IsProfileImageLoaded) 
     { 
      pvm.RetrieveProfileImage(); 
     } 
    } 
} 

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

** EDIT **

Существует и еще одна странная ошибка происходит. В приведенном ниже коде:

 public void GetBitmap(int imageID, Action<BitmapImage> callback) 
     { 
      // Get from server 
      bitmapCallback = callback; 

      memorialFileServiceClient.GetImageCompleted += new EventHandler<GetImageCompletedEventArgs>(OnGetBitmapHandler); 
      memorialFileServiceClient.GetImageAsync(imageID); 
     } 

     public void OnGetBitmapHandler(object sender, GetImageCompletedEventArgs imageArgs) 
     { 
      if (!imageArgs.Cancelled) 
      { 
       // I get cross-thread error right here 
       System.Windows.Media.Imaging.BitmapImage bi = new BitmapImage(); 
       ConvertToBitmapFromBuffer(bi, imageArgs.Result.Image); 

       // call call back 
       bitmapCallback.Invoke(bi); 
      } 
     } 

я получаю ошибку кросс-нить при попытке создать новый объект BitmapImage в фоновом потоке. Почему я не могу создать новый объект BitmapImage в фоновом потоке?

ответ

62

Для того, чтобы обновить DependencyProperty в ViewModel, использовать один и тот же диспетчер вы будете использовать, чтобы получить доступ к любому другому UIElement:

System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => {...}); 

Кроме того, BitmapImages необходимо создать в потоке пользовательского интерфейса. Это связано с тем, что он использует DependencyProperties, который может использоваться только в потоке пользовательского интерфейса. Я попытался создать экземпляр BitmapImages для отдельных потоков, и он просто не работает. Вы можете попытаться использовать некоторые другие средства для хранения изображений в памяти. Например, при загрузке изображения сохраните его в MemoryStream. Затем BitmapImage в потоке пользовательского интерфейса может установить его источник в MemoryStream.

Вы можете попробовать создать экземпляр BitmapImages в потоке пользовательского интерфейса, а затем сделать все остальное с помощью BitmapImage в другом потоке ... но это будет волосатое, и я даже не уверен, что это сработает. Ниже приведен пример:

System.Windows.Media.Imaging.BitmapImage bi = null; 
using(AutoResetEvent are = new AutoResetEvent(false)) 
{ 
    System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => 
    { 
     bi = new BitmapImage(); 
     are.Set(); 
    }); 
    are.WaitOne(); 
} 

ConvertToBitmapFromBuffer(bi, imageArgs.Result.Image); 
bitmapCallback.Invoke(bi); 
+0

Великий, это именно то, что я искал. Я пошел с байт [] как средство хранения. – Ender

+0

это также помогло мне, когда мне пришлось поднять событие PropertyChanged для нескольких свойств после того, как он был установлен. –

+0

Я не понимал, что BitmapImage необходимо сделать в потоке пользовательского интерфейса. Я действительно должен был подумать об этом, так как DependencyProperty будет проблемой в противном случае. :: facepalm :: – Paul

2

Я считаю, что проблема с перекрестной резьбой связана с потоком пользовательского интерфейса.

Редактирование связанного объекта может привести к обновлению пользовательского интерфейса рабочего потока, что не может быть выполнено. Вероятно, вам придется выполнять InvokeRequired/Invoke hokey-pokey всякий раз, когда вы обновляете связанный класс.

Вы сказали, что это уже знали, но для справки:

MSDN on thread-safe calls to UI

0

Этого можно достичь с помощью WriteableBitmap.

public void LoadThumbAsync(Stream src, 
        WriteableBitmap bmp, object argument) 
    { 
     ThreadPool.QueueUserWorkItem(callback => 
     { 
      bmp.LoadJpeg(src); 
      src.Dispose(); 
      if (ImageLoaded != null) 
      { 
       Deployment.Current.Dispatcher.BeginInvoke(() => 
       { 
        ImageLoaded(bmp, argument); 
       }); 
      } 
     }); 
    } 

Но Вы должны построить WriteableBitmap в UI тему, то загрузка может быть выполнена в другом потоке.

void DeferImageLoading(Stream imgStream) 
    { 
     // we have to give size 
     var bmp = new WriteableBitmap(80, 80); 
     imageThread.LoadThumbAsync(imgStream, bmp, this); 
    } 

Смотреть больше explanaition на этом blog post

+0

Я не нашел объяснений в связанном «сообщении блога». Может быть, исходный адрес уже был повторно использован для чего-то другого - или запись хорошо скрыта – juhariis

+0

juhariis, у меня есть ссылка на блог, извините за неудобства – Ernest

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