Canvas InvalidateVisual() 线程异常
Canvas InvalidateVisual() thread exception
我正在开发一个使用 Canvas.
显示一些图像(带有一些滤镜效果)的应用程序
我有一个名为 RendererBooster
的静态 class。此 class' RenderImage()
方法在背景上渲染具有给定效果的图像 WITH TASK 并设置 MyViewer
coltrol 的 _bSource
属性 与渲染图像。 (MyViewer 派生自Canvas)
另一方面,我在 MyViewer
class 中有一个 DispatcherTimer
。此 DispatcherTimes
每 2 毫秒 滴答一次 并检查 _bSource
是否为 NOT NULL,调用 Canvas' InvalidateVisual()
方法。
一切都很好,直到这里。
我重写的 OnRender()
方法只是将 _bSource
绘制到屏幕并将 _bSource
设置为 NULL。之后,我得到 Cannot use a DependencyObject that belongs to a different thread than its parent Freezable
异常。这是一些示例代码。我能做些什么来修复它?
RendererBooster
public static class RendererBooster
{
public static void RenderImage()
{
MyViewer viewer = ViewerManager.GetViewer();
Task.Factory.StartNew(() =>
{
unsafe
{
// render
// render again
// render again ..
// ...
// when rendering is done, set the _bSource.
viewer._bSource = BitmapSource.Create(sizeDr.Width, sizeDr.Height, 96, 96, PixelFormats.Prgba64, null, mlh.Buffer, sStride * sizeDr.Height, sStride);
}
});
}
}
MyViewer
public class MyViewer : Canvas
{
public BitmapSource _bSource = null;
private object _lockObj = new object();
public MyViewer()
{
DispatcherTimer dt = new DispatcherTimer();
dt.Interval = TimeSpan.FromMilliseconds(2);
dt.Tick += dt_Tick;
dt.Start();
}
void dt_Tick(object sender, EventArgs e)
{
if (_bSource == null)
return;
InvalidateVisual();
}
protected override void OnRender(DrawingContext dc)
{
lock (_lockObj)
{
dc.DrawImage(_bSource, new System.Windows.Rect(new System.Windows.Point(0, 0), new System.Windows.Size(ActualWidth, ActualHeight)));
_bSource = null;
// this is the line that i get the exception
//Cannot use a DependencyObject that belongs to a different thread than its parent Freezable
}
}
}
注意: 为什么我在另一个函数上进行渲染工作/class?因为渲染需要 3-4 秒。如果我在 OnRender() 方法内渲染,UIThread 会冻结应用程序。
BitmapSource
class继承Freezable
,进而继承DependencyObject
。如您所知,DependencyObject
具有线程亲和性(因为它们继承了 DispatcherObject
)。也就是说,每个 DependencyObject
首先检查您是否被允许使用 CheckAccess()
and VerifyAccess()
方法从当前线程访问它。
在您的示例中,您在工作线程中创建了一个 BitmapSource
(使用 Task
),但尝试在另一个线程中使用它:将调用 OnRender()
方法在 UI 话题上。
因此,一种解决方案是在 UI 线程中创建您的 BitmapSource
。你可以使用例如Dispatcher.Invoke()
或 SynchronizationContext.Post()
方法。如果您使用的是 .NET 4.5+,我建议您将 Task
代码更改为:
public static async Task RenderImage()
{
MyViewer viewer = ViewerManager.GetViewer();
await Task.Run(() =>
{
// your rendering code
})
.ContinueWith(t =>
{
// BitmapSource creating code
},
TaskScheduler.FromCurrentSynchronizationContext());
}
使用这种方法,您的耗时渲染将在工作线程上处理,但 BitmapSource
对象创建将在调用 UI 线程上进行。但是,您必须确保不安全对象的线程安全。
此外,我建议您将 _bSource
字段设为私有。使用 lock
语句将对它的访问包装在 属性 中。对于您当前的实现,多线程同步将无法正常工作(您在不使用 lock
的情况下分配值)。
// don't have to initialize it with null, because all .NET reference objects
// will be initialized with their default value 'null' automatically
private BitmapSource _bSource;
public BitmapSource BSource
{
get { lock (_lockObj) { return this._bSource; } }
set { lock (_lockObj) { this._bSource = value; } }
}
你必须在任何地方使用这个 属性,包括你的 MyViewer
class 方法。这样做,您将可以安全地在多线程环境中访问该对象:
void dt_Tick(object sender, EventArgs e)
{
if (this.BSource == null)
return;
// ...
}
如果这对你来说太复杂了,我也有一个更简单的解决方案。
关于 Freezable
s 有一点值得一提:
Thread safety: a frozen Freezable object can be shared across threads.
因此,您可以在创建后冻结 BitmapSource
对象以允许跨线程访问它:
BitmapSource source = BitmapSource.Create(/* arguments... */);
source.Freeze();
viewer.BSource = source;
我正在开发一个使用 Canvas.
显示一些图像(带有一些滤镜效果)的应用程序我有一个名为 RendererBooster
的静态 class。此 class' RenderImage()
方法在背景上渲染具有给定效果的图像 WITH TASK 并设置 MyViewer
coltrol 的 _bSource
属性 与渲染图像。 (MyViewer 派生自Canvas)
另一方面,我在 MyViewer
class 中有一个 DispatcherTimer
。此 DispatcherTimes
每 2 毫秒 滴答一次 并检查 _bSource
是否为 NOT NULL,调用 Canvas' InvalidateVisual()
方法。
一切都很好,直到这里。
我重写的 OnRender()
方法只是将 _bSource
绘制到屏幕并将 _bSource
设置为 NULL。之后,我得到 Cannot use a DependencyObject that belongs to a different thread than its parent Freezable
异常。这是一些示例代码。我能做些什么来修复它?
RendererBooster
public static class RendererBooster
{
public static void RenderImage()
{
MyViewer viewer = ViewerManager.GetViewer();
Task.Factory.StartNew(() =>
{
unsafe
{
// render
// render again
// render again ..
// ...
// when rendering is done, set the _bSource.
viewer._bSource = BitmapSource.Create(sizeDr.Width, sizeDr.Height, 96, 96, PixelFormats.Prgba64, null, mlh.Buffer, sStride * sizeDr.Height, sStride);
}
});
}
}
MyViewer
public class MyViewer : Canvas
{
public BitmapSource _bSource = null;
private object _lockObj = new object();
public MyViewer()
{
DispatcherTimer dt = new DispatcherTimer();
dt.Interval = TimeSpan.FromMilliseconds(2);
dt.Tick += dt_Tick;
dt.Start();
}
void dt_Tick(object sender, EventArgs e)
{
if (_bSource == null)
return;
InvalidateVisual();
}
protected override void OnRender(DrawingContext dc)
{
lock (_lockObj)
{
dc.DrawImage(_bSource, new System.Windows.Rect(new System.Windows.Point(0, 0), new System.Windows.Size(ActualWidth, ActualHeight)));
_bSource = null;
// this is the line that i get the exception
//Cannot use a DependencyObject that belongs to a different thread than its parent Freezable
}
}
}
注意: 为什么我在另一个函数上进行渲染工作/class?因为渲染需要 3-4 秒。如果我在 OnRender() 方法内渲染,UIThread 会冻结应用程序。
BitmapSource
class继承Freezable
,进而继承DependencyObject
。如您所知,DependencyObject
具有线程亲和性(因为它们继承了 DispatcherObject
)。也就是说,每个 DependencyObject
首先检查您是否被允许使用 CheckAccess()
and VerifyAccess()
方法从当前线程访问它。
在您的示例中,您在工作线程中创建了一个 BitmapSource
(使用 Task
),但尝试在另一个线程中使用它:将调用 OnRender()
方法在 UI 话题上。
因此,一种解决方案是在 UI 线程中创建您的 BitmapSource
。你可以使用例如Dispatcher.Invoke()
或 SynchronizationContext.Post()
方法。如果您使用的是 .NET 4.5+,我建议您将 Task
代码更改为:
public static async Task RenderImage()
{
MyViewer viewer = ViewerManager.GetViewer();
await Task.Run(() =>
{
// your rendering code
})
.ContinueWith(t =>
{
// BitmapSource creating code
},
TaskScheduler.FromCurrentSynchronizationContext());
}
使用这种方法,您的耗时渲染将在工作线程上处理,但 BitmapSource
对象创建将在调用 UI 线程上进行。但是,您必须确保不安全对象的线程安全。
此外,我建议您将 _bSource
字段设为私有。使用 lock
语句将对它的访问包装在 属性 中。对于您当前的实现,多线程同步将无法正常工作(您在不使用 lock
的情况下分配值)。
// don't have to initialize it with null, because all .NET reference objects
// will be initialized with their default value 'null' automatically
private BitmapSource _bSource;
public BitmapSource BSource
{
get { lock (_lockObj) { return this._bSource; } }
set { lock (_lockObj) { this._bSource = value; } }
}
你必须在任何地方使用这个 属性,包括你的 MyViewer
class 方法。这样做,您将可以安全地在多线程环境中访问该对象:
void dt_Tick(object sender, EventArgs e)
{
if (this.BSource == null)
return;
// ...
}
如果这对你来说太复杂了,我也有一个更简单的解决方案。
关于 Freezable
s 有一点值得一提:
Thread safety: a frozen Freezable object can be shared across threads.
因此,您可以在创建后冻结 BitmapSource
对象以允许跨线程访问它:
BitmapSource source = BitmapSource.Create(/* arguments... */);
source.Freeze();
viewer.BSource = source;