Rendering a program icon to the Image Control using TPL - c#

Hi i'm trying to create an Interactivity.Behavior to load a program's icon in the background. The following is the code but it gave me The calling thread cannot access this object because a different thread owns it..
protected override void OnAttached()
{
base.OnAttached();
if (!string.IsNullOrEmpty(Url))
{
Icon ico = Icon.ExtractAssociatedIcon(Url);
if (ico != null)
{
taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => {
MemoryStream ms = new MemoryStream();
ico.ToBitmap().Save(ms, ImageFormat.Png);
ms.Position = 0;
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = ms;
bi.EndInit();
return bi;
}).ContinueWith((t) => AssociatedObject.Source = t.Result, taskScheduler);
}
}
}

WPF objects (anything that descends from DispatcherObject) are thread-affine -- normally they can only be used on the thread that created them. This includes BitmapImage objects. If you create the BitmapImage on a background thread, then it can only be used from that background thread -- which means that the UI thread will get an error when it tries to display the bitmap.
However, BitmapImage descends from Freezable. Freezable has a Freeze method that will make the instance read-only. And per the "Freezable Objects Overview" on MSDN:
A frozen Freezable can also be shared across threads, while an unfrozen Freezable cannot.
So if you add a call to bi.Freeze(); just before you return the image from your background task, then you should be able to use the image successfully from your UI thread.

Although you are using CurrentSynchronizationContext, give it a try if may have to be run on the Dispatcher of the icon....
ico.Dispatcher.BeginInvoke(
new Action(
() =>
{
ico.ToBitmap().Save(ms, ImageFormat.Png);
///rest of the code that uses `ms`.
}));
Suggestion: Why arent you using Priority Binding and Binding.IsAsync for slow loading images....
http://social.msdn.microsoft.com/Forums/en-AU/wpf/thread/b3dc9baa-4cf6-49ed-a316-b9fb1cd29516

Related

Export png of UserControl in Background Thread

I try to export in Png a WPF UserControl that I made in a background Thread.
The rendering works great in the UI thread, but I can't run my spinner for user waiting correctly because the two controls run in the UI thread.
When I run my code in the backgroung thread I have the following exception :
System.Windows.Markup.XamlParseException: 'The calling thread cannot access this object because a different thread owns it'
I use the following code to create the thread :
List<Byte[]> images = null;
Thread thread = new Thread(() =>
{
HorizontalViewDesigner horizontalViewDesigner = new HorizontalViewDesigner(true);
horizontalViewDesigner.ItemsSource = new ObservableCollection<ICanvasSelection>(elements);
horizontalViewDesigner.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
horizontalViewDesigner.Arrange(new Rect(new Size(10_000, 10_000)));
Size size = new Size(1024d, 1024d);
images = horizontalViewDesigner.GetImages(size);
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
thread = null;
return (images);
The exception occurs in the GetImages method on the call of
userControl.UpdateLayout();
It's seems that this method update Dependency Properties from binding.
Any solution ?
Thanks

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

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.

Updating bitmapSource - copying one to another

This is my exception
The calling thread cannot access this object because a different thread owns it.
my function get results from a calculation and I want to update an already opened window..
public override void UpdateResult(BaseMetricResults result)
{
var newResults = result as MetricUniformityResults;
if (newResults == null)
{
return;
}
DispatcherHelper.UIDispatcher.Invoke(() =>
{
TopToBottomGraph.CrossSectionPoints.Clear();
foreach (var point in newResults.TopToBottomGraph.CrossSectionPoints)
{
TopToBottomGraph.CrossSectionPoints.Add(point);
}
newResults.JetMap.Freeze(); //exception here
byte[] arr = new byte[(int) (newResults.JetMap.Width*newResults.JetMap.Height*3)];
newResults.JetMap.CopyPixels(arr, (int) (newResults.JetMap.Width*3), 0);
JetMap = BitmapSource.Create((int) newResults.JetMap.Width, (int) newResults.JetMap.Height, 96, 96,
PixelFormats.Rgb24, BitmapPalettes.WebPalette, arr,
(int) (newResults.JetMap.Width*3));
});
}
This is my last attempt I'm not sure if I have to freeze the bitmapsource or not...
Anyway newResults.JetMap is BitmapSource, and I have a property named JetMap which is the new BitmapSource, how can I update the old image with the new one?
You need to call Jetmap.Freeze(); right after you create it and not inside the dispatcher, once its its frozen you can set it inside the dispatcher and you wont get an exception
Your DispatcherHelper.UIDispatcher.Invoke method will execute on the UI thread. My best guess is that the newResults.JetMap bitmap was created on a different thread which is preventing you from modifying it. At the same time, you can't create the JetMap bitmap that you want to show on a thread other than the UI thread. So without more context, the best suggestion would be to ensure that the newResults.JetMap bitmap is also created in the main UI thread.

Loading a web image asynchronously

I've encountered a problem with displaying an image from the web in my WPF app:
I used a BitmapImage for this task. My first attempt was to execute it in the UI thread but I quickly understood that's a no-no since the application became unresponsive until the image was completely loaded. My second attempt was to use a BackgroudWorker:
var worker = new BackgroundWorker();
worker.DoWork += worker_LoadImage;
worker.RunWorkerCompleted+=worker_RunWorkerCompleted;
worker.RunWorkerAsync(someURI);
and the worker functions:
private void worker_LoadImage(object sender, DoWorkEventArgs e)
{
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnDemand;
image.UriSource = e.Argument as Uri;
image.DownloadFailed += new EventHandler<ExceptionEventArgs>(image_DownloadFailed);
image.EndInit();
e.Result = image;
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//if I understand correctly, this code runs in the UI thread so the
//access to the component image1 is valid.
image1.Source = e.Result as BitmapImage;
}
after that, I still got an InvalidOperationException: "The calling thread cannot access this object because a different thread owns it."
I've researched a bit and found out that since BitmapImage is Freezable, I have to call Freeze before accessing the object from another thread.
So I've tried to replace the last row in worker_LoadImage with:
image.Freeze();
e.Result = image;
But then I got an exception that my image cannot be frozen, I found out that it's probably
because the image wasn't done being downloaded when I tried to invoke Freeze(). So I added the following code to the image creation:
image.DownloadCompleted += image_DownloadCompleted;
where:
void image_DownloadCompleted(object sender, EventArgs e)
{
BitmapImage img = (BitmapImage)sender;
img.Freeze();
}
So now we get to the real question:
How do I make the background worker to wait until the image is completely downloaded and the event is fired?
I've tried many things: looping while the image's isDownloading is true, Thread.Sleep, Thread.Yield, Semaphores, Event wait handles and more.
I dont know how the image downloading actually works behind the scenes but what happens when I try one of the methods above is that the image never finishes to download (isDownloading is stuck on True)
Is there a better, simpler way to achieve the rather simple task im trying to accomplish?
Some things to notice:
this answer actually works, but only once: when I try to load another image it says the dispatcher is closed. Even after reading a bit about Dispatchers, I don't really understand how the OP achieved that or if it's possible to extend the solution for more than one image.
When I put a message box before the worker exits his DoWork function, I click OK and the image apears which means the download continued while the message box was opened and finished before I clicked OK.
Given that you're using the bitmap's ability to asynchronously load the image you don't need a BackgroundWorker in the first place. Rather than creating a BGW to start an asynchronous operation and wait for it to finish, just use the asynchronous operation directly.
All you have to do is update the UI from your image_DownloadCompleted handler (after freezing the image) and you no longer need a BGW anymore:
private void FetchImage(Uri uri)
{
var context = SynchronizationContext.Current;
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnDemand;
image.UriSource = uri;
image.DownloadFailed += image_DownloadFailed;
image.DownloadCompleted += (s, args) =>
{
image.Freeze();
context.Post(_ => image1.Source = image, null);
};
image.EndInit();
}

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