2009-12-17 8 views
26

Ho due classi ViewModel: PersonViewModel e PersonSearchListViewModel. Uno dei campi di implementazione di PersonViewModel è un'immagine del profilo che viene scaricata tramite WCF (memorizzata nella cache locale in una memoria isolata). PersonSearchListViewModel è una classe contenitore che contiene un elenco di Persone. Dal momento che il caricamento delle immagini è relativamente pesante, PersonSearchListViewModel carica solo le immagini per la pagina corrente, successiva e precedente (i risultati sono paginati sull'interfaccia utente) ... per migliorare ulteriormente il carico di immagini metto il carico di immagini su un altro thread. Tuttavia, l'approccio multi-threading causa problemi di accesso cross-thread.Problema di accesso incrociato non valido

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

come elaborare i dati in classe ViewModel in maniera multi-threaded? So che devi utilizzare il dispatcher sull'interfaccia utente per l'aggiornamento. Come aggiorni ViewModel associato a UI?

** EDIT **

C'è anche un altro errore di strano accade. Nel codice riportato di seguito:

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

Viene visualizzato un errore cross-thread durante il tentativo di creare un nuovo oggetto BitmapImage nel thread in background. Perché non riesco a creare un nuovo oggetto BitmapImage su un thread in background?

risposta

62

Al fine di aggiornare un DependencyProperty in un ViewModel, utilizzare lo stesso dispatcher si usa per accedere a qualsiasi altra UIElement:

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

Inoltre, BitmapImages deve essere istanziato sul thread dell'interfaccia utente. Questo perché utilizza DependencyProperties, che può essere utilizzato solo sul thread dell'interfaccia utente. Ho provato a istanziare BitmapImages su thread separati e semplicemente non funziona. Potresti provare a utilizzare altri mezzi per memorizzare le immagini in memoria. Ad esempio, quando si scarica l'immagine, salvarla in un MemoryStream. Quindi un BitmapImage sul thread dell'interfaccia utente può impostare la sua origine su MemoryStream.

È possibile provare a creare un'istanza di BitmapImages sul thread dell'interfaccia utente e quindi eseguire tutto il resto con BitmapImage su un altro thread ... ma ciò diventerebbe un problema e non sono nemmeno sicuro che funzionerebbe. Quello che segue è un esempio:

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

Grande, che è esattamente quello che stavo cercando. Sono andato con byte [] come mezzo di archiviazione. – Ender

+0

anche questo mi ha aiutato quando ho dovuto generare un evento PropertyChanged per più proprietà dopo che ne è stato impostato uno. –

+0

Non ho realizzato un BitmapImage necessario per il thread dell'interfaccia utente. Avrei davvero dovuto pensarci, dal momento che una DependencyProperty sarebbe stata un problema altrimenti. :: facepalm :: – Paul

2

Credo che si stia verificando un problema di cross threading con il thread dell'interfaccia utente.

La modifica dell'oggetto associato può forzare un aggiornamento dell'interfaccia utente sul thread di lavoro, che non può riuscire. Probabilmente dovrai eseguire InhokeRequired/Invoke hokey-pokey ogni volta che aggiorni la classe associata.

Hai detto che sapeva già, ma per riferimento:

MSDN on thread-safe calls to UI

0

Può essere raggiunto con 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); 
       }); 
      } 
     }); 
    } 

Ma si deve costruire WriteableBitmap in UI thread, quindi il caricamento può essere eseguita in un altro thread.

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

Tutte explanaition su questo blog post

+0

Non ho trovato alcuna spiegazione nel "post del blog" collegato. Potrebbe essere che l'indirizzo originale è già stato riutilizzato per qualcos'altro - o la voce è ben nascosta – juhariis

+0

juhariis, ho aggiornato il link del blog, scusa per l'inconveniente – Ernest