0

に書き込もうとしてWritableBitmapいますが、非 UI スレッドでデータ処理を行いたいと考えています。Lockそのため、UI ディスパッチャからメソッドとメソッドを呼び出してUnlockおり、残りは別のスレッドで実行されます。

IntPtr pBackBuffer = IntPtr.Zero;

Application.Current.Dispatcher.Invoke(new Action(() =>
    {
        Debug.WriteLine("{1}: Begin Image Update: {0}", DateTime.Now, this.GetHashCode());

        _mappedBitmap.Lock();

        pBackBuffer = _mappedBitmap.BackBuffer;
    }));

// Long processing straight on pBackBuffer...

Application.Current.Dispatcher.Invoke(new Action(()=>
{
        Debug.WriteLine("{1}: End Image Update: {0}", DateTime.Now, this.GetHashCode());

        // the entire bitmap has changed
        _mappedBitmap.AddDirtyRect(new Int32Rect(0, 0, _mappedBitmap.PixelWidth,
                                                    _mappedBitmap.PixelHeight));

        // release the back buffer and make it available for display
        _mappedBitmap.Unlock();
}));

このコードは、必要に応じて UI ディスパッチャを明確に呼び出すため、任意のスレッドから呼び出すことができます。これは、私のコントロールが大きなストレスを受けていないときに機能します。しかし、これを 100 ミリ秒ごとに呼び出すと、すぐに次のメッセージが表示InvalidOperationExceptionされます。AddDirtyRect

{"画像のロックが解除されている間は、このメソッドを呼び出すことはできません。"}

これがどのように起こるのか理解できません。私のデバッグ出力ログLockは、私のクラスのこのインスタンスに対して実際に呼び出されたことを示しています。

アップデート

私のシナリオ全体: WPFImageコントロールで浮動小数点行列を表示できるようにするクラスを作成しています。このクラスFloatingPointImageSourceAdapterでは、API を使用してデータを設定できます

void SetData(float[] data, int width, int height)

ImageSourceまた、ImageコントロールSouceプロパティをバインドできるを公開します。

内部的には、これは を使用して実装されてWritableBitmapいます。ユーザーが新しいデータを設定するたびに、ピクセルを処理してバッファに再書き込みする必要があります。BackBufferデータは高頻度で設定される予定なので、 を呼び出す代わりに に直接書き込むことにしましたWritePixels。さらに、ピクセルの再マッピングには時間がかかる可能性があり、画像が非常に大きくなる可能性があるため、別のスレッドで処理を実行したいと考えています。

フレームを落とすことで高いストレスに対処することにしました。したがってAutoResetEvent、ユーザーがいつデータの更新を要求したかを追跡する があります。そして、実際の作業を行うバックグラウンド タスクがあります。

class FloatingPointImageSourceAdapter
{
    private readonly AutoResetEvent _updateRequired = new AutoResetEvent(false);

    public FloatingPointImageSourceAdapter()
    {
        // all sorts of initializations

        Task.Factory.StartNew(UpdateImage, TaskCreationOptions.LongRunning);
    }

    public void SetData(float[] data, int width, int height)
    {
        // save the data

        _updateRequired.Set();
    }

    private void UpdateImage()
    {
        while (true)
        {
            _updateRequired.WaitOne();

            Debug.WriteLine("{1}: Update requested from thread {2}, {0}", DateTime.Now, this.GetHashCode(), Thread.CurrentThread.ManagedThreadId);

            IntPtr pBackBuffer = IntPtr.Zero;

            Application.Current.Dispatcher.Invoke(new Action(() =>
                {
                    Debug.WriteLine("{1}: Begin Image Update: {0}", DateTime.Now, this.GetHashCode());

                    _mappedBitmap.Lock();

                    pBackBuffer = _mappedBitmap.BackBuffer;
                }));

            // The processing of the back buffer

            Application.Current.Dispatcher.Invoke(new Action(() =>
            {
                Debug.WriteLine("{1}: End Image Update: {0}", DateTime.Now, this.GetHashCode());

                // the entire bitmap has changed
                _mappedBitmap.AddDirtyRect(new Int32Rect(0, 0, _mappedBitmap.PixelWidth,
                                                            _mappedBitmap.PixelHeight));

                // release the back buffer and make it available for display
                _mappedBitmap.Unlock();
            }));
        }
    }
}

勇気を出すために、ここでは多くのコードを削除しました。

SetData私のテストは、特定の間隔内で呼び出すタスクを作成します。

private void Button_Click_StartStressTest(object sender, RoutedEventArgs e)
{
    var sleepTime = SleepTime;

    _cts = new CancellationTokenSource();

    var ct = _cts.Token;

    for (int i = 0; i < ThreadsNumber; ++i)
    {
        Task.Factory.StartNew(() =>
        {
            while (true)
            {
                if (ct.IsCancellationRequested)
                {
                    break;
                }

                int width = RandomGenerator.Next(10, 1024);
                int height = RandomGenerator.Next(10, 1024);

                var r = new Random((int)DateTime.Now.TimeOfDay.TotalMilliseconds);
                var data = Enumerable.Range(0, width * height).Select(x => (float)r.NextDouble()).ToArray();

                this.BeginInvokeInDispatcherThread(() => FloatingPointImageSource.SetData(data, width, height));

                Thread.Sleep(RandomGenerator.Next((int)(sleepTime * 0.9), (int)(sleepTime * 1.1)));
            }
        }, _cts.Token);
    }
}

このテストを withThreadsNumber=1と with で実行するSleepTime=100と、前述の例外でクラッシュします。

更新 2

コマンドが実際にシリアルに実行されることを確認してみました。別のプライベート フィールドを追加しました

private int _lockCounter;

そして、whileループでそれを操作します:

private void UpdateImage()
{
    while (true)
    {
        _updateRequired.WaitOne();

        Debug.Assert(_lockCounter == 0);
        _lockCounter++;

        IntPtr pBackBuffer = IntPtr.Zero;

        Application.Current.Dispatcher.Invoke(new Action(() =>
            {
                Debug.Assert(_lockCounter == 1);
                ++_lockCounter;

                _mappedBitmap.Lock();

                pBackBuffer = _mappedBitmap.BackBuffer;
            }));

        Debug.Assert(pBackBuffer != IntPtr.Zero);

        Debug.Assert(_lockCounter == 2);

        ++_lockCounter;

        // Process back buffer

        Debug.Assert(_lockCounter == 3);
        ++_lockCounter;

        Application.Current.Dispatcher.Invoke(new Action(() =>
        {
            Debug.Assert(_lockCounter == 4);
            ++_lockCounter;

            // the entire bitmap has changed
            _mappedBitmap.AddDirtyRect(new Int32Rect(0, 0, _mappedBitmap.PixelWidth,
                                                        _mappedBitmap.PixelHeight));

            // release the back buffer and make it available for display
            _mappedBitmap.Unlock();


        }));

        Debug.Assert(_lockCounter == 5);
        _lockCounter = 0;
    }
}

メッセージの順序が何らかの形で台無しにされた場合、私Debug.Assertの s がこれをキャッチすることを期待していました。しかし、カウンターのあるものはすべて問題ありません。それらはシリアルロジックに従って正しくインクリメントされますが、それでも例外が発生しAddDirtyRectます。

4

2 に答える 2

1

Application.Current.Dispatcher.Invoke は、デリゲートとして渡されたメソッドを UI スレッド自体で実行しようとします。これは、UI スレッドが空いているときに発生します。これで命令を連続して実行しようとすると、UI スレッドで操作を実行するのとほとんど変わりません。Application.Current.Dispatcher.Invoke で実行される命令は常に非常に最小限にする必要があります。たとえば、UI の値のみを変更するのは 1 行だけにする必要があります。そのため、Dispatcher の一部として実行される複雑な操作を避け、Dispatcher の外に移動し、UI を更新する操作のみを実行します。

于 2013-10-27T09:00:32.577 に答える
0

したがって、いくつかの(非常に長い)掘り下げた後、本当のバグは、勇気のために省略したコードに隠されていることがわかりました:-)

私のクラスでは、画像のサイズを変更できます。データを設定するとき、新しいサイズが古いサイズと同じかどうかを確認し、そうでない場合は新しい を初期化しWritableBitmapます。

whileループの途中で画像のサイズが (別のスレッドを使用して) 変更されたことが原因でした。そして、これにより、処理コードのさまざまな段階でさまざまなインスタンスが処理されました_mappedBitmap(_mappedBitmapさまざまな段階でさまざまなインスタンスが指されたため)。そのため、インスタンスが新しいものに変更されたときに、ロック解除された状態で作成されたため、(正当な) 例外が発生しました。

于 2013-10-27T12:08:22.670 に答える