0

我正在开发一个使用 Canvas 显示一些图像(带有一些滤镜效果)的应用程序。

我有一个名为RendererBooster. 此类的RenderImage()方法在背景上使用给定的WITH TASKMyViewer效果渲染图像,并使用渲染图像设置coltrol 的_bSource属性。(MyViewer 来源于 Canvas

另一方面,我DispatcherTimerMyViewer课堂上。这每 2 毫秒打勾DispatcherTimes并检查是否为 NOT NULL,调用 Canvas 的方法。_bSourceInvalidateVisual()

一切都很好,直到这里。

我的重写OnRender()方法只是将其绘制_bSource到屏幕上并设置_bSource为 NULL。在那之后,我得到了Cannot use a DependencyObject that belongs to a different thread than its parent Freezable例外。这是一些示例代码。我能做些什么来修复它?

渲染器助推器

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

我的查看器

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

注意:为什么我要在其他函数/类上进行渲染工作?因为渲染需要 3-4 秒。如果我在 OnRender() 方法中渲染,UIThread 会冻结应用程序。

4

1 回答 1

2

BitmapSource类继承,而Freezable后者又继承DependencyObject。如您所知,DependencyObjects 具有线程亲和性(因为它们继承DispatcherObject)。也就是说,每个DependencyObjectfirst 都会检查是否允许您使用CheckAccess()andVerifyAccess()方法从当前线程访问它。

在您的示例中,您BitmapSource在工作线程中创建 a (使用 a Task),但尝试在另一个中使用它:该OnRender()方法将在 UI 线程上调用。

因此,一种解决方案可能是BitmapSource在 UI 线程中创建您的。您可以为此使用例如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使用语句将访问权限包装在属性中。对于您当前的实现,多线程同步将无法正常工作(您在不使用 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; } }
}

你必须在任何地方使用这个属性,包括你的MyViewer类方法。这样做,您将可以安全地在多线程环境中访问对象:

void dt_Tick(object sender, EventArgs e)
{
    if (this.BSource == null)
        return;
    // ...
}

如果它对你来说太复杂了,我也有一个更简单的解决方案。关于s有一点Freezable要提:

线程安全:可以跨线程共享冻结的 Freezable 对象。

因此,您可以BitmapSource在创建后冻结您的对象以允许跨线程访问它:

BitmapSource source = BitmapSource.Create(/* arguments... */);
source.Freeze();
viewer.BSource = source;  
于 2015-03-19T09:26:34.867 回答