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

倖福魔咒の 提交于 2019-12-08 03:58:39

问题


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

回答1:


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



回答2:


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.




回答3:


You should use Dispatcher Property within Window. The Invoke method helps to swap you code to the GUI thread. Something like:

Dispather.Invoke(()=>{ YourCodeinMainTreadHere();})



回答4:


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
    }



回答5:


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.



来源:https://stackoverflow.com/questions/8201276/trying-to-update-image-control-source-from-other-tread-and-getting-error

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!