Oz Shimon
Oz Shimon

Reputation: 21

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;
        }
    }
}

Upvotes: 1

Views: 2849

Answers (5)

makwana.a
makwana.a

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

ebb
ebb

Reputation: 9387

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));
            }
        }
    }
}

Upvotes: 2

Ryan Durrant
Ryan Durrant

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

0xBADF00D
0xBADF00D

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

Rohit Vats
Rohit Vats

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

Related Questions