试图从其他方面更新图像控制源并得到错误

本文关键字:错误 控制 图像 其他 方面 更新 | 更新日期: 2023-09-27 18:15:17

我的项目是关于捕获全屏和更新图像控制这个图像;

在最后一行(image1.Source = img;),我得到了错误:

The calling thread cannot access this object because a different thread owns it.

代码:

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

试图从其他方面更新图像控制源并得到错误

解决方案

传入SwitchPic的图像由另一个线程拥有。只需将if(!image1.Dispatcher.CheckAccess())行更改为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;
    }
}

如何改进

让我们从清除那些while循环开始,因为它们会吞噬你的CPU。

    而不是在你的UpdateQueue方法周围包装一个while循环,然后创建Timer .
  • BlockingCollection<T>代替Queue<T>这是为并发访问而做的——这样你就消除了第二个无限while循环。

以上实际上是生产者/消费者模式的配方:

    Timer使用的线程是我们的生产者,因为它添加调用UpdateScreen的线程是我们的消费者,因为它从集合中删除项。

你的代码示例已经使用了这种模式(有点),但是当集合中没有项目时,它不能阻塞线程。与简单地用BlockingCollection<T>Take方法阻塞线程相比,你做的是Thread.Sleep(30),这有巨大的性能开销。

进一步,我们可以简单地删除SwitchPic方法,并将其合并到UpdateScreen中,因为将其作为一个单独的方法没有实际意义-它只能从UpdateScreen方法调用。

我们不再需要检查图像上的CheckAccess,因为图像总是由Timer使用的线程创建的(Timer使用的线程是一个特殊的线程,因此不能被其他任何人使用)。此外,UpdateScreen运行在专用线程上,消除了对CheckAccess的需求。

因为我假设您希望图像按顺序显示,所以我使用Dispatcher.Invoke而不是Dispathcer.BeginInvoke

代码看起来像:

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

由于您的picQueue is created on main thread所以队列和解队列操作抛出错误。

将此操作放在主线程的分配器上以解决thread affinity

你应该在Window中使用Dispatcher属性。Invoke方法有助于将代码交换到GUI线程。比如:

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

您可以创建一个全局变量theBitmap,然后而不是设置image1。来源= img;在同一行设置theBitmap = img;然后使用定时器

private void timer1_Tick(object sender, EventArgs e)
    {
         image1.source = theBitmap
    }

如果你正在处理一个线程,那么我会建议你使用静态资源(对象数据提供程序)来更新另一个线程中的值。

因为静态资源对所有线程都是可用的,你可以从任何线程更改它们的值。并将图像的图像属性绑定到此静态资源。当一个静态资源被更新时,它也会更新图像。我已经用这种方法来更新进度条值,所以我认为它也会在这里工作。