私が作成した1つのスレッドがソース(実際にはデジタルカメラ)から画像を継続的に取得し、GUIのパネル(panel.Image = img)に配置するC#デスクトップアプリケーションがあります(これは別のスレッドである必要があります)これは、コントロールのコードビハインドです。
アプリケーションは動作しますが、一部のマシンでは、ランダムな時間間隔で次のエラーが発生します(予測不可能)
************** Exception Text **************
System.InvalidOperationException: The object is currently in use elsewhere.
次に、パネルが赤い十字、赤いXに変わります。これは、プロパティから編集できる無効な画像アイコンだと思います。アプリケーションは動作し続けますが、パネルが更新されることはありません。
このエラーは、画像に何か他のものを描画するコントロールのonpaintイベントが原因であることがわかります。
そこでロックを使ってみましたが、運がありませんでした:(
パネルに画像を配置する関数を呼び出す方法は次のとおりです。
if (this.ReceivedFrame != null)
{
Delegate[] clients = this.ReceivedFrame.GetInvocationList();
foreach (Delegate del in clients)
{
try
{
del.DynamicInvoke(new object[] { this,
new StreamEventArgs(frame)} );
}
catch { }
}
}
これはデリゲートです:
public delegate void ReceivedFrameEventHandler(object sender, StreamEventArgs e);
public event ReceivedFrameEventHandler ReceivedFrame;
これは、制御コードビハインド内の関数がそれに登録する方法です。
Camera.ReceivedFrame +=
new Camera.ReceivedFrameEventHandler(camera_ReceivedFrame);
私も試しました
del.Method.Invoke(del.Target, new object[] { this, new StreamEventArgs(b) });
の代わりに
del.DynamicInvoke(new object[] { this, new StreamEventArgs(frame) });
しかし運がない
このエラーを修正する方法、または少なくとも何らかの方法でエラーをキャッチして、スレッドに画像をパネルにもう一度配置させる方法を知っている人はいますか?
これは、Gdi + Imageクラスがスレッドセーフではないためです。ただし、ペイントや画像サイズの取得など、画像にアクセスする必要があるたびにロックを使用することで、InvalidOperationExceptionを回避できます。
Image DummyImage;
// Paint
lock (DummyImage)
e.Graphics.DrawImage(DummyImage, 10, 10);
// Access Image properties
Size ImageSize;
lock (DummyImage)
ImageSize = DummyImage.Size;
ところで、上記のパターンを使用する場合は、呼び出しは必要ありません。
同じエラーメッセージで同様の問題が発生しましたが、ビットマップをロックしても何も修正されませんでした。すると、静的なブラシを使って形を描いていることに気づきました。案の定、スレッドの競合を引き起こしたのはブラシでした。
var location = new Rectangle(100, 100, 500, 500);
var brush = MyClass.RED_BRUSH;
lock(brush)
e.Graphics.FillRectangle(brush, location);
これは私の場合と学んだ教訓でうまくいきました。スレッドの競合が発生している時点で使用されているすべての参照型を確認してください。
同じCameraオブジェクトが数回使用されているようです。
例えば。受信したフレームごとに新しいバッファを使用してみてください。ピクチャーボックスが新しいフレームを描画している間、キャプチャライブラリがそのバッファを再びいっぱいにするように思えます。したがって、高速のマシンではこれは問題にならない可能性があり、低速のマシンでは問題になる可能性があります。
私は一度同様のプログラムを作成しました。各受信フレームの後で、次のフレームの受信を要求し、その要求に[〜#〜] new [〜#〜]フレーム受信バッファーを設定する必要がありました。
それができない場合は、最初にカメラから受信したフレームを新しいバッファーにコピーしてそのバッファーをキューに追加するか、2つの交互のバッファーを使用しますそしてオーバーランをチェックします。 myOutPutPanel.BeginInvokeを使用してcamera_ReceivedFrameメソッドを呼び出すか、キューをチェックするスレッドを実行して、新しいエントリがある場合はmnyOutPutPanel.BeginInvokeを呼び出してメソッドを呼び出し、新しいバッファーをパネル上の画像として設定します。
さらに、バッファを受け取ったら、Panel Invokeメソッドを使用して画像の設定を呼び出します(キャプチャライブラリのスレッドではなく、ウィンドウスレッドで実行されることを保証します)。
以下の例は、任意のスレッド(キャプチャライブラリまたは他の個別のスレッド)から呼び出すことができます。
void camera_ReceivedFrame(object sender, StreamEventArgs e)
{
if(myOutputPanel.InvokeRequired)
{
myOutPutPanel.BeginInvoke(
new Camera.ReceivedFrameEventHandler(camera_ReceivedFrame),
sender,
e);
}
else
{
myOutPutPanel.Image = e.Image;
}
}
これはマルチスレッドの問題だと思いますWindowsゴールデンルールを使用し、メインスレッド使用パネルのパネルを更新します。これを呼び出すと、クロススレッドの例外が克服されます。