How to create a BitmapImage buffer in background thread on C#? [duplicate] - c#

This question already has answers here:
Error: Must create DependencySource on same Thread as the DependencyObject even by using Dispatcher
(2 answers)
The calling thread cannot access this object because a different thread owns it.How do i edit the image?
(3 answers)
How do you pass a BitmapImage from a background thread to the UI thread in WPF?
(3 answers)
Closed 1 year ago.
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;
});
}

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://learn.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.
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/freezable-objects-overview?view=netframeworkdesktop-4.8
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.

Related

Async / await, create a new UI element error [duplicate]

This question already has answers here:
The calling thread must be STA, because many UI components require this error In WPF. On form.show()
(3 answers)
Closed 6 years ago.
I have the following issue with my simplified code (WPF) below:
System.InvalidOperationException' in PresentationCore.dll
The calling thread must be STA, because many UI components require this
Would be so kind to help me to correct my code.
void CrearBtnNews()
{
KinectTileButton botontest = new KinectTileButton
{
Style = FindResource("KinectTileButtonStyle1") as Style,
Content = "WeB",
Height = 265,
Width = 450,
Background = null,
BorderBrush = null
};
botontest.Click +=
async (o, args) =>
{
await Task.Run(()=> BrowserAsync());
};
}
private void BrowserAsync()
{
Grid gridx = new Grid();//////// ERROR in this line ///////////////
System.Threading.Thread.Sleep(8000);
MessageBox.Show("working 8 seg");
}
All UI-related things must be done in the main UI thread. You are trying to create an UI element in a background thread, which is a no-go.
If you want to do some long calculations etc. in the background, you should do only that, and then return the data to the main thread and create the UI controls there.
Something like this: (ResultStruct is madeup)
button.Click += async(o,args) =>
{
ResultStruct data = await Task.Run(() => Browser());
Grid gridx = new Grid();
// set the data to the grid
};
private ResultStruct Browser()
{
// calculations, working ...
return data;
}
Also, method BrowserAsync is not actually async, you are just calling it in an async task, so I renamed it to just Browser.
There's no need to wrap the entire method in Task.Run. Instead, you should only be wrapping 'the work' in Task.Run, and handle the creation of UI components on the UI thread. Though, if the creation of components is a lot, you can also wrap that in an async called (as shown in the example).
Note: The reason why you are getting the error is because you are trying to create a UI component (the Grid) on a separate thread. All UI specific stuff must be created on a Dispatcher thread.
void CrearBtnNews()
{
KinectTileButton botontest = new KinectTileButton
{
Style = FindResource("KinectTileButtonStyle1") as Style,
Content = "WeB",
Height = 265,
Width = 450,
Background = null,
BorderBrush = null
};
botontest.Click += async (o, args) =>
{
Grid gridx = new Grid();
await BrowserAsync();
MessageBox.Show("working 8 seg");
};
}
private async Task BrowserAsync()
{
// Do work here
//
await Task.Run(() =>
{
System.Threading.Thread.Sleep(8000);
});
}

Thread makes application halt

I'm currently trying to create a FileViewer control, and after I've added Items (Filenames with Icons, size, etc) to my ListView (Icon - Filename - Extension - Size) I also check if the file is an image (png/jpg, etc) but this I do on a different Thread.
My expectation of a thread is that it runs beside the main application, but after I've added all my files I start this thread. It checks all files in the ListView and creates thumbnails for them. If done correctly, ListView icons should appear one after one as they're loaded - but they're not. They all appear at the same time.
...and I can't do anything while the Thread is active.
Why is this happening and what am I doing wrong? I've dealt with Threads before and it's always worked, I invoke the method with a Callback.
Flow of the Thread:
Format file key = "C:\image.png" = "C_image_png".
Check if thumbnail to image exists (by checking it's key), then use it
Else load thumbnail with Image.FromFile().GetThumbnailImage() and add image with Key to Listview's images
Finally change the ImageKey of the ListView item.
All done in a thread.
private void GetFiles()
{
// Load all files in directory
Thread t = new Thread(new ThreadStart(GetImageFiles));
t.Priority = ThreadPriority.Lowest;
t.Start();
}
delegate void GetImageFilesCallback();
private void GetImageFiles()
{
if (this.IsHandleCreated)
{
if (files.InvokeRequired)
{
GetImageFilesCallback callback = new GetImageFilesCallback(GetImageFiles);
this.Invoke(callback);
}
else
{
string extension = "";
string key = "";
foreach (string file in _files)
{
extension = FileManager.GetExtension(file);
key = (DirectoryCurrent + file).Replace(":", "").Replace("\\", "_").Replace(".", "_");
foreach (string knownimages in _knownImageTypes)
{
if (extension.ToLower() == knownimages)
{
foreach (ListViewItem item in files.Items)
{
if (item.Text == file)
{
if (files.SmallImageList != null)
{
if (files.SmallImageList.Images[key] == null)
{
files.SmallImageList.Images.Add(key, Image.FromFile(DirectoryCurrent + file).GetThumbnailImage(16, 16, null, IntPtr.Zero));
files.LargeImageList.Images.Add(key, Image.FromFile(DirectoryCurrent + file).GetThumbnailImage(32, 32, null, IntPtr.Zero));
}
files.Items[item.Index].ImageKey = key;
}
}
}
}
}
}
files.Refresh();
}
}
}
The method that your thread calls is invoking itself onto the main thread, and then doing all the work in that thread, thereby blocking your UI.
You should arrange your code so that the thread code does not touch the ListView, but just loads each image, then invokes a main-thread method, passing the bitmaps so that the main thread can assign them to the ListView.
Here's a sketch of what I mean:
// this is your thread method
// it touches no UI elements, just loads files and passes them to the main thread
private void LoadFiles(List<string> filenames) {
foreach (var file in filenames) {
var key = filename.Replace(...);
var largeBmp = Image.FromFile(...);
var smallBmp = Image.FromFile(...);
this.Invoke(new AddImagesDelegate(AddImages), key, largeBmp, smallBmp);
}
}
// this executes on the main (UI) thread
private void AddImages(string key, Bitmap large, Bitmap small) {
// add bitmaps to listview
files.SmallImageList.Images.Add(key, small);
files.LargeImageList.Images.Add(key, large);
}
private delegate AddImagesDelegate(string key, Bitmap large, Bitmap small);
Read the following: http://www.codeproject.com/Articles/10311/What-s-up-with-BeginInvoke. The important thing with Invoke and BeginInvoke is that they both operate on the Main thread. BeginInvoke just doesn't wait for the message to be processed before returning control. Eventually though, the work will happen on the Main thread and will block until it is complete.

thread and event [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on
WPF access GUI from other thread
Good day ,
I write class
public class Metric1
{
public event MetricUnitEventHandler OnUnitRead;
public void ReiseEventOnUnitRead(string MetricUnitKey)
{
if (OnUnitRead!=null)
OnUnitRead(this,new MetricUnitEventArgs(MetricUnitKey));
}
.....
}
Metric1 m1 = new Metric1();
m1.OnUnitRead += new MetricUnitEventHandler(m1_OnUnitRead);
void m1_OnUnitRead(object sender, MetricUnitEventArgs e)
{
MetricUnits.Add(((Metric1)sender));
lstMetricUnit.ItemsSource = null;
lstMetricUnit.ItemsSource = MetricUnits;
}
Then i start new thread that every minute calls m1's ReiseEventOnUnitRead method.
In row lstMetricUnit.ItemsSource = null; throws excepition - "The calling thread cannot access this object because a different thread owns it." Why?
You cannot change GUI item from another thread that isn't the GUI thread,
If you are working with WinForms use Invoke and InvokeRequired.
if (lstMetricUnit.InvokeRequired)
{
// Execute the specified delegate on the thread that owns
// 'lstMetricUnit' control's underlying window handle.
lstMetricUnit.Invoke(lstMetricUnit.myDelegate);
}
else
{
lstMetricUnit.ItemsSource = null;
lstMetricUnit.ItemsSource = MetricUnits;
}
If you are working with WPF use Dispatcher.
lstMetricUnit.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Normal,
new Action(
delegate()
{
lstMetricUnit.ItemsSource = null;
lstMetricUnit.ItemsSource = MetricUnits;
}
));
You should use Dispatcher.
Example:
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, (Action)(() => {
lstMetricUnit.ItemsSource = null;
lstMetricUnit.ItemsSource = MetricUnits;
})));
In WPF and Forms -> you cannot modify UI controls from different thread.

using the objects which are created by other thread

I just want to change the window's background in another thread. there are two program, one is work right, and the other throw an InvalidOperationException.
The right code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Thread t = new Thread(new ParameterizedThreadStart(threadTest));
t.Start(#"C:\Users\Public\Pictures\Sample Pictures\Chrysanthemum.jpg");
}
void threadTest(object obj)
{
string path = obj as string;
this.Dispatcher.Invoke(new Func<object>(() => this.Background = new
}
}
the Error Code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Thread t = new Thread(new ParameterizedThreadStart(threadTest));
t.Start(#"C:\Users\Public\Pictures\Sample Pictures\Chrysanthemum.jpg");
}
void threadTest(object obj)
{
string path = obj as string;
//this.Dispatcher.Invoke(new Func<object>(() => this.Background = new ImageBrush(new BitmapImage(new Uri(path)))));
ImageBrush background = new ImageBrush(new BitmapImage(new Uri(path)));
this.Dispatcher.Invoke(new Func<object>(() => this.Background = background));
}
}
the different between these codes is that, the error code create the ImageBrush object in the child thread.
So my question is that: in the wpf program, is the thread can only use the objects creates by own thread?
thanks for any reply.
Yes, you are right. Only the UI thread can use objects created by it. So, you can use the Dispatcher to "enqueue" the UI operations on it's proper thread.
Answering your second question, sure, there's a way to "pass" objects to the UI Thread. If you see the BeginInvoke structure (of the Dispatcher) it's:
public DispatcherOperation BeginInvoke(
Delegate d,
params Object[] args
)
Where the args is the params object array, there's where you put the params.
Now, if you are using some Freezable object (for example some Image, Brush, Transform or Geometry) then you need to object.Freeze(); before send it to the UI Thread.
Yes, correct, It's not only about WPF, but in general, about Windows programming.
You can not update UI object from other thread different from its own.
The reason for this is simply because, the message pumping and especially delivery to destination control of OS must be guaranteed. This is naturally valid for communication using SendMesage, but for PostMessage too.
If You create an object on a separate thread, You might use it on gui thread if You freeze it first. See Freezable objects.

Trying to update image control source from other tread and getting Error

My project is about capturing the full screen and updating the image control by this images;
At the last line (image1.Source = img;) I get the error:
The calling thread cannot access this object because a different thread owns it.
the code:
public partial class MainWindow : Window
{
delegate void MyDel(BitmapImage img);
Queue<BitmapImage> picQueue = new Queue<BitmapImage>();
public MainWindow()
{
InitializeComponent();
Thread updateTrd = new Thread(new ThreadStart(UpdateQueue));
updateTrd.Start();
Thread PicTrd = new Thread(new ThreadStart(UpdateScreen));
PicTrd.Start();
}
private void UpdateQueue()
{
while (true)
{
ScreenCapture sc = new ScreenCapture();//this function provide a desktop screenshot
System.Drawing.Image img = sc.CaptureScreen();
Stream stream = new MemoryStream();
img.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
BitmapImage image = new BitmapImage();
image.BeginInit();
image.StreamSource = stream;
image.EndInit();
picQueue.Enqueue(image);
}
}
private void UpdateScreen()
{
while (true)
{
if (picQueue.Count > 0)
{
SwitchPic(picQueue.Dequeue());
}
else
{
Thread.Sleep(30);
}
}
}
private void SwitchPic(BitmapImage img)
{
if(!image1.Dispatcher.CheckAccess())
{
this.image1.Dispatcher.BeginInvoke(new MyDel(SwitchPic), img);
}
else
{
image1.Source = img;
}
}
}
The solution
The image passed into SwitchPic is owned by another thread. Simply change the line if(!image1.Dispatcher.CheckAccess()) to if(!img.Dispatcher.CheckAccess()).
private void SwitchPic(BitmapImage img)
{
if(!img.Dispatcher.CheckAccess())
{
this.image1.Dispatcher.BeginInvoke(new MyDel(SwitchPic), img);
}
else
{
image1.Source = img;
}
}
How to improve
Let's start with getting those while loops out of the way, since they'll eat your CPU.
Instead of wrapping a while loop around your UpdateQueue method,
then create a Timer.
Instead of using a Queue<T>, then use a BlockingCollection<T>
which is made for concurrent access - this way you eliminate the
second infinity while loop.
The above is actually the recipe of a producer/consumer pattern:
The thread used by the Timer is our producer, since it adds
items to the collection.
The thread that invokes UpdateScreen is our consumer, since it
removes items from the collection.
Your code example already uses this pattern (kinda), however it's not able to block the thread when no items are in the collection. Instead you're doing Thread.Sleep(30) which haves a huge perfomance overhead, compared to simply blocking the thread with Take method from BlockingCollection<T>.
Further more, we can simply remove the SwitchPic method, and merge it into UpdateScreen, since it makes no real sense to have this as a seperate method - it's only invoked from the UpdateScreen method.
We don't have to check for CheckAccess anymore on the image, because the image is always created by the thread the Timer uses (The thread the Timer uses is a special thread, and can therefore not be used by anyone else). Also that the UpdateScreen runs on a dedicated thread, eliminates the need for the CheckAccess.
As I assume that you want the images to display in order, I'm using Dispatcher.Invoke and not Dispathcer.BeginInvoke.
The code then looks like:
using System.IO;
using System.Windows;
using System.Threading;
using System.Windows.Media.Imaging;
using System.Collections.Concurrent;
using System;
namespace WpfApplication3
{
public partial class MainWindow : Window
{
private BlockingCollection<BitmapImage> pictures = new BlockingCollection<BitmapImage>();
public MainWindow()
{
InitializeComponent();
var takeScreen = new Timer(o => TakeScreenshot(), null, 0, 10);
new Thread(new ThreadStart(UpdateScreen)).Start();
}
private void TakeScreenshot()
{
var sc = new ScreenCapture();
var img = sc.CaptureScreen();
var stream = new MemoryStream();
img.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
var image = new BitmapImage();
image.BeginInit();
image.StreamSource = stream;
image.EndInit();
pictures.Add(image);
}
private void UpdateScreen()
{
while (true)
{
var item = pictures.Take(); // blocks if count == 0
item.Dispatcher.Invoke(new Action(() => image1.Source = item));
}
}
}
}
Since your picQueue is created on main thread so queue and dequeue operation throwing an error. Put this operation on dipatcher for main thread to reslove the thread affinity.
You should use Dispatcher Property within Window. The Invoke method helps to swap you code to the GUI thread. Something like:
Dispather.Invoke(()=>{ YourCodeinMainTreadHere();})
You could create a global variable theBitmap, and then instead of setting the image1.Source = img; in the same line set theBitmap = img; Then use a timer such that
private void timer1_Tick(object sender, EventArgs e)
{
image1.source = theBitmap
}
If you are working with a thread then I will suggest you to use a static resource (Object Data Provider) to update values in another thread.
Because Static Resources are available to all threads, and you can change their values from any thread. And bind an image property of an image to this static resource. When a static resource gets updated it will update the image too. I have used this way to update a progress bar value, so I think it will work here too.

Categories

Resources