试图从其他方面更新图像控制源并得到错误
本文关键字:错误 控制 图像 其他 方面 更新 | 更新日期: 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。
- 而不是在你的
- 用
BlockingCollection<T>
代替Queue<T>
这是为并发访问而做的——这样你就消除了第二个无限while循环。
UpdateQueue
方法周围包装一个while循环,然后创建Timer
.以上实际上是生产者/消费者模式的配方:
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
}
如果你正在处理一个线程,那么我会建议你使用静态资源(对象数据提供程序)来更新另一个线程中的值。
因为静态资源对所有线程都是可用的,你可以从任何线程更改它们的值。并将图像的图像属性绑定到此静态资源。当一个静态资源被更新时,它也会更新图像。我已经用这种方法来更新进度条值,所以我认为它也会在这里工作。