に書き込もうとして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
ます。