I'm developing an app that shows some images (with some filter effects) using Canvas.
I've a static class called RendererBooster
. This class' RenderImage()
method renders the image with given effects WITH TASK on background and sets the MyViewer
coltrol's _bSource
property with the rendered image. (MyViewer is derived from Canvas)
On the other hand, I've a DispatcherTimer
inside the MyViewer
class. This DispatcherTimes
ticks every 2ms and checks if _bSource
is NOT NULL, calls the Canvas' InvalidateVisual()
method.
Everything is fine untill here.
My overridden OnRender()
method just draws that _bSource
to screen and sets _bSource
to NULL. After that, I get Cannot use a DependencyObject that belongs to a different thread than its parent Freezable
exception. Here is some sample code. What can I do to fix it?
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
}
}
}
NOTE: Why I'm doing the rendering work on an other function / class? Because rendering takes 3-4 seconds. If I render inside the OnRender() method, UIThread freezes the application.
The BitmapSource
class inherits Freezable
, which in turn inherits DependencyObject
. As you know, DependencyObject
s have thread affinity (because they inherit DispatcherObject
). That is, every DependencyObject
first checks whether you are allowed to access it from current thread using the CheckAccess()
and VerifyAccess()
methods.
In your example, you create a BitmapSource
in the worker thread (using a Task
), but try to use it in the other: the OnRender()
method will be called on the UI thread.
So, one solution could be to create your BitmapSource
in the UI thread. You could use e.g. Dispatcher.Invoke()
or SynchronizationContext.Post()
methods for this. If you're using .NET 4.5+, I would suggest you to change your Task
code to something like:
public static async Task RenderImage()
{
MyViewer viewer = ViewerManager.GetViewer();
await Task.Run(() =>
{
// your rendering code
})
.ContinueWith(t =>
{
// BitmapSource creating code
},
TaskScheduler.FromCurrentSynchronizationContext());
}
With this approach, your time consuming rendering will be processed on a worker thread, but the BitmapSource
object creation will occur on the calling UI thread. You have to ensure the thread safety for your unsafe objects, however.
Furthermore, I would suggest you to make the _bSource
field private. Wrap the access to it in a property with a lock
statement. With your current implementation, the multithreading synchronization will not work properly (you assign the value without using a 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; } }
}
You have to use this property everywhere, including your MyViewer
class methods. Doing so, you will be safe in accessing the object in a multithreading environment:
void dt_Tick(object sender, EventArgs e)
{
if (this.BSource == null)
return;
// ...
}
If it's too complicated for you, I've a simpler solution too.
There is one thing about Freezable
s to mention:
Thread safety: a frozen Freezable object can be shared across threads.
So, you just could freeze your BitmapSource
object after creation to allow a cross-thread access to it:
BitmapSource source = BitmapSource.Create(/* arguments... */);
source.Freeze();
viewer.BSource = source;