Hello I want to create a buffer of BitmapImage built with a Queue from a background worker. The main target is to load some images from a private network and don't block UI. I can create that buffer without blocking UI but when I try to get one image from the queue I get an System.InvalidOperationException
, so I'm trying to access an object owned by another thread.
My worker code:
private Task<Queue<BitmapImage>> BufferLoader(List<ReadLabel> labels)
{
return Task<Queue<BitmapImage>>.Factory.StartNew(() =>
{
var parameters = CommonData.CurrentRecipe.Parameters["validation"];
var buffer = new Queue<BitmapImage>();
foreach (var label in labels)
{
var fileName = $"{CommonData.CurrentFiche.RegId}-{label.ReadPosition}.bmp";
var validationPath = parameters[ValidationParameters.Parameters.sValidationImagesPath.ToString()].Value.ToString();
var fullPath = Path.Combine(Properties.Settings.Default.CameraImagesBasePath, validationPath, fileName);
try
{
if (File.Exists(fullPath))
{
buffer.Enqueue(new BitmapImage(new Uri(fullPath, UriKind.Absolute)));
}
else
{
throw new ValidationImageNotFoundException(fullPath);
}
}
catch { }
}
return buffer;
});
}
Calling method:
private async void LoadValidationImages()
{
var imageList = new List<ReadLabel>(CommonData.CurrentFiche.Labels);
var images = imageList.FindAll(f => f.CoscNumber.StartsWith("err"));
if (images.Count > 0)
{
Queue<BitmapImage> result = await BufferLoader(images);
ImageBuffer = new Queue<BitmapImage>(result);
BufferLoadingCompleted();
}
}
UI thread call method:
private void BufferLoadingCompleted()
{
/*Dispatcher.Invoke(() =>
{*/
imgToValidate.Source = ImageBuffer.Peek();
var parameters = CommonData.CurrentRecipe.Parameters["validation"];
rotation = parameters[ValidationParameters.Parameters.ImageRotation.ToString()].ValueToDouble();
scaleX = parameters[ValidationParameters.Parameters.ImageScaleX.ToString()].ValueToDouble();
scaleY = parameters[ValidationParameters.Parameters.ImageScaleY.ToString()].ValueToDouble();
scrlImage.ScrollToHorizontalOffset(parameters[ValidationParameters.Parameters.ScrollerHorizontalFactor.ToString()].ValueToDouble());
scrlImage.ScrollToVerticalOffset(scrollPosVertical = parameters[ValidationParameters.Parameters.ScrollerVerticalFactor.ToString()].ValueToDouble());
ApplyTransformations();
Console.WriteLine("Load finished");
//});
}
I tried to use Dispatcher.Invoke
on BufferLoadingCompleted()
but it don't work I get the same exception. What I'm doing wrong?
Final code. Solution as suggested by Andy:
In my background worker code I didn't Freeze()
the new objects created inside the working thread, so I was getting an exception.
Solution applies only to the background worker method:
private Task<Queue<BitmapImage>> BufferLoader(List<ReadLabel> labels)
{
return Task<Queue<BitmapImage>>.Factory.StartNew(() =>
{
var parameters = CommonData.CurrentRecipe.Parameters["validation"];
var buffer = new Queue<BitmapImage>();
foreach (var label in labels)
{
var fileName = $"{CommonData.CurrentFiche.RegId}-{label.ReadPosition}.bmp";
var validationPath = parameters[ValidationParameters.Parameters.sValidationImagesPath.ToString()].Value.ToString();
var fullPath = Path.Combine(Properties.Settings.Default.CameraImagesBasePath, validationPath, fileName);
try
{
if (File.Exists(fullPath))
{
var newImage = new BitmapImage(new Uri(fullPath, UriKind.Absolute));
newImage.Freeze();
buffer.Enqueue(newImage);
}
else
{
throw new ValidationImageNotFoundException(fullPath);
}
}
catch { }
}
return buffer;
});
}
CodePudding user response:
You're creating something on a non ui thread that by default has thread affinity.
Luckily though, a bitmapimage inherits from freezable. See the inheritance chain:
https://docs.microsoft.com/en-us/dotnet/api/system.windows.media.imaging.bitmapimage?view=net-5.0
Inheritance: Object DispatcherObject DependencyObject Freezable Animatable ImageSource BitmapSource BitmapImage
If you call .Freeze() on a freezable then you can pass it between threads.
From there:
What Is a Freezable?
A Freezable is a special type of object that has two states: unfrozen and frozen. When unfrozen, a Freezable appears to behave like any other object. When frozen, a Freezable can no longer be modified.
A Freezable provides a Changed event to notify observers of any modifications to the object. Freezing a Freezable can improve its performance, because it no longer needs to spend resources on change notifications. A frozen Freezable can also be shared across threads, while an unfrozen Freezable cannot.