2009-12-17 5 views
26

J'ai deux classes ViewModel: PersonViewModel et PersonSearchListViewModel. L'un des champs que PersonViewModel implémente est une image de profil qui est téléchargée via WCF (mise en cache localement dans un stockage isolé). PersonSearchListViewModel est une classe de conteneur qui contient une liste de personnes. Puisque le chargement des images est relativement lourd, PersonSearchListViewModel ne charge que les images de la page courante, suivante et précédente (les résultats sont paginés sur l'interface utilisateur) ... pour améliorer la charge des images, je charge les images sur un autre thread. Toutefois, l'approche multi-thread entraîne des problèmes d'accès inter-thread.Problème d'accès à un thread non valide

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(); 
     } 
    } 
} 

Comment traiter les données que vous en classe ViewModel de manière multi-thread? Je sais que vous devez utiliser Dispatcher sur l'interface utilisateur pour mettre à jour. Comment mettez-vous à jour ViewModel qui est lié à l'interface utilisateur?

** EDIT **

Il y a aussi une erreur plus étrange qui se passe. Dans le code ci-dessous:

 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); 
      } 
     } 

Je reçois une erreur cross-thread lors de la tentative de création d'un nouvel objet BitmapImage dans le thread d'arrière-plan. Pourquoi ne puis-je pas créer un nouvel objet BitmapImage sur un thread d'arrière-plan?

Répondre

62

Afin de mettre à jour un DependencyProperty dans un ViewModel, utilisez le même répartiteur que vous utilisez pour accéder à tout autre UIElement:

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

De plus, BitmapImages doit être instancié sur le thread d'interface utilisateur. En effet, il utilise DependencyProperties, qui ne peut être utilisé que sur le thread d'interface utilisateur. J'ai essayé d'instancier BitmapImages sur des threads séparés et cela ne fonctionne tout simplement pas. Vous pouvez essayer d'utiliser d'autres moyens pour stocker des images en mémoire. Par exemple, lorsque vous téléchargez l'image, stockez-la dans un MemoryStream. Ensuite, une image BitmapImage sur le thread d'interface utilisateur peut définir sa source sur le MemoryStream. Vous pouvez essayer d'instancier BitmapImages sur le thread de l'interface utilisateur et ensuite faire tout le reste avec BitmapImage sur un autre thread ... mais cela deviendrait poilu et je ne suis même pas sûr que cela fonctionnerait. Ce qui suit serait un exemple:

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

Grand, qui est exactement ce que je cherchais. Je suis allé avec byte [] comme un moyen de stockage. – Ender

+0

cela m'a également aidé quand j'ai dû déclencher un événement PropertyChanged pour plusieurs propriétés après avoir été défini. –

+0

Je ne me suis pas rendu compte qu'un BitmapImage devait être fait sur le fil de l'interface utilisateur. J'aurais vraiment dû y penser car un DependencyProperty serait un problème autrement. :: facepalm :: – Paul

2

Je crois que vous avez un problème de threading croisé avec le thread UI.

La modification de l'objet lié peut forcer une mise à jour de l'interface utilisateur sur le thread de travail, qui ne peut aboutir. Vous devrez probablement exécuter InvokeRequired/Invoke hokey-pokey chaque fois que vous mettez à jour la classe liée.

Vous avez dit que vous saviez déjà, mais pour référence:

MSDN on thread-safe calls to UI

0

Il peut être réalisé avec 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); 
       }); 
      } 
     }); 
    } 

Mais vous devez construire WriteableBitmap dans l'interface utilisateur fil, puis le chargement peut être effectué dans un autre fil.

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

Voir plus explanaition sur cette blog post

+0

Je n'ai trouvé aucune explication dans le "blog" lié. Pourrait être que l'adresse d'origine a déjà été réutilisée à autre chose - ou l'entrée est bien cachée – juhariis

+0

juhariis, j'ai mis à jour le lien blog, désolé pour le désagrément – Ernest

Questions connexes