c# - Problema de acceso a hilos cruzados no válido
silverlight multithreading (3)
Creo que estás teniendo problemas con el hilo de la interfaz de usuario.
La edición del objeto enlazado puede forzar una actualización de la interfaz de usuario en el hilo de trabajo, que no puede tener éxito. Es probable que necesites hacer InvokeRequired / Invoke hokey-pokey siempre que actualices la clase encuadernada.
Dijiste que ya lo sabías, pero para referencia:
Tengo dos clases de ViewModel: PersonViewModel y PersonSearchListViewModel. Uno de los campos que implementa PersonViewModel es una imagen de perfil que se descarga a través de WCF (en caché localmente en almacenamiento aislado). PersonSearchListViewModel es una clase contenedor que contiene una lista de Personas. Debido a que cargar imágenes es relativamente pesado, PersonSearchListViewModel carga solo imágenes para la página actual, la siguiente y la anterior (los resultados se muestran en la interfaz de usuario) ... para mejorar aún más la carga de imágenes, coloco la carga de imágenes en otra secuencia. Sin embargo, el enfoque multi-threading causa problemas de acceso entre hilos.
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();
}
}
}
¿Cómo se procesan los datos en la clase ViewModel de manera multihilo? Sé que debes usar el despachador en la interfaz de usuario para actualizar. ¿Cómo se actualiza ViewModel que está vinculado a la interfaz de usuario?
** EDIT **
También hay otro error extraño que está sucediendo. En el código a continuación:
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);
}
}
Obtengo un error de cruce de hilos al intentar crear un nuevo objeto BitmapImage en el hilo de fondo. ¿Por qué no puedo crear un nuevo objeto BitmapImage en una cadena de fondo?
Para actualizar una DependencyProperty en un ViewModel, use el mismo despachador que usaría para acceder a cualquier otro UIElement:
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => {...});
Además, BitmapImages debe crearse una instancia en el subproceso de UI. Esto se debe a que usa DependencyProperties, que solo se puede usar en el subproceso de la interfaz de usuario. He intentado crear instancias de BitmapImages en hilos separados y simplemente no funciona. Podría tratar de usar otros medios para almacenar imágenes en la memoria. Por ejemplo, cuando descargue la imagen, guárdela en un MemoryStream. Luego, una BitmapImage en el subproceso de la interfaz de usuario puede establecer su origen en el MemoryStream.
Puedes tratar de crear instancias de BitmapImages en el hilo de la interfaz de usuario y luego hacer todo lo demás con BitmapImage en otro hilo ... pero eso se pondrá pesado y ni siquiera estoy seguro de que funcione. El siguiente sería un ejemplo:
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);
Se puede lograr 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);
});
}
});
}
Pero debe construir WriteableBitmap en UI Thread, luego la carga se puede realizar en otro hilo.
void DeferImageLoading( Stream imgStream )
{
// we have to give size
var bmp = new WriteableBitmap(80, 80);
imageThread.LoadThumbAsync(imgStream, bmp, this);
}
Ver más explicaciones en esta publicación de blog