0

Canvas を使用していくつかの画像 (いくつかのフィルター効果付き) を表示するアプリを開発しています。

という静的クラスがありますRendererBooster。このクラスのRenderImage()メソッドは、背景にWITH TASKMyViewerを指定して画像をレンダリングし、レンダリングされた画像でcoltrol の_bSourceプロパティを設定します。( MyViewer は Canvas から派生しています)

一方、私はクラスのDispatcherTimer中にいます。MyViewerこれは 2ms ごとに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)。つまり、メソッドとメソッドDependencyObjectを使用して現在のスレッドからアクセスできるかどうかを最初にチェックします。CheckAccess()VerifyAccess()

あなたの例でBitmapSourceは、ワーカー スレッドで ( a を使用してTask) を作成しますが、他のスレッドで使用しようとしOnRender()ます。メソッドは UI スレッドで呼び出されます。

したがって、1 つの解決策はBitmapSource、UI スレッドで作成することです。これにはegDispatcher.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クラスメソッドを含め、どこでもこのプロパティを使用する必要があります。そうすることで、マルチスレッド環境でオブジェクトに安全にアクセスできます。

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

複雑すぎる場合は、もっと簡単な解決策もあります。Freezables について言及すべきことが 1 つあります。

スレッド セーフ: 凍結された Freezable オブジェクトはスレッド間で共有できます。

したがって、作成後にオブジェクトをフリーズしてBitmapSource、クロススレッド アクセスを許可することができます。

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