Search code examples
wpfcanvasbitmapsource

Canvas InvalidateVisual() thread exception


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.


Solution

  • The BitmapSource class inherits Freezable, which in turn inherits DependencyObject. As you know, DependencyObjects 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 Freezables 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;