Reputation: 21
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;
}
}
}
Upvotes: 1
Views: 2849
Reputation: 309
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.
Upvotes: 0
Reputation: 9387
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;
}
}
Let's start with getting those while loops out of the way, since they'll eat your CPU.
UpdateQueue
method,
then create a Timer
.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:
Timer
is our producer, since it adds
items to the collection.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));
}
}
}
}
Upvotes: 2
Reputation: 1040
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
}
Upvotes: 0
Reputation: 1034
You should use Dispatcher Property within Window. The Invoke method helps to swap you code to the GUI thread. Something like:
Dispather.Invoke(()=>{ YourCodeinMainTreadHere();})
Upvotes: 0
Reputation: 81313
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
.
Upvotes: 0