Laden...

Zugreifen auf GUI-Element aus anderem Thread

Erstellt von C#-Newbie vor 11 Jahren Letzter Beitrag vor 11 Jahren 10.243 Views
Thema geschlossen
C
C#-Newbie Themenstarter:in
3 Beiträge seit 2012
vor 11 Jahren
Zugreifen auf GUI-Element aus anderem Thread

Hallo c#-Gemeinde

Ich weis, meine Frage scheint banal, aber ich bin jetzt seit letzten Donnerstag am googlen und suchen und ich kapier es einfach nicht. Mir bringt auch ein Link zur Klassenbibliothek nichts und zu Tutorials, diese habe ich bisher durchgeforstet und ich sehe nicht, wo mein Fehler ist.

Mir geht es um den Zugriff auf GUI-Elemente einer WPF-Anwendung von einem anderen Thread aus. Ich will hier immer wenn eine Berechnung fertig ist diese in einem Image darstellen, sprich das Image aktualisieren. Da diese Berechnung länger dauert habe ich diese aus dem UI-Thread ausgelagert.

Zum Kontext des ganzen. Ich habe eine Microsoft Kinect. Diese sendet mir Bilder und löst somit ein Event (sensor_DepthFrameReady) aus. Mit den Pixeldaten will ich anschließend ein paar Berechnungen durchführen. Deshalb starte ich in diesem Event den BackgroundWorker. Dieser führt dann in "erstelleMatrix"und "berechneVarianz" die Berechnungen durch und am Ende von "berechneVarianz" soll das Image aktualisiert werden. An dieser Stelle kommt jedoch immer der Fehler "Der aufrufende Thread kann nicht auf dieses Objekt zugreifen, da sich das Objekt im Besitz eines anderen Threads befindet."

Fehlermeldung:> Fehlermeldung:

System.InvalidOperationException wurde nicht behandelt.
HResult=-2146233079
Message=Der aufrufende Thread kann nicht auf dieses Objekt zugreifen, da sich das Objekt im Besitz eines anderen Threads befindet.
Source=WindowsBase
StackTrace:
bei System.Windows.Threading.Dispatcher.VerifyAccess()
bei System.Windows.Freezable.ReadPreamble()
bei System.Windows.Media.Imaging.BitmapSource.get_PixelWidth()
bei QualityGate1.MainWindow.<>c__DisplayClass4.<berechneVarianz>b__3() in C:\Users\Tobias\documents\visual studio 2010\Projects\QualityGate1\QualityGate1\MainWindow.xaml.cs:Zeile 436.
bei System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
bei MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
bei System.Windows.Threading.DispatcherOperation.InvokeImpl()
bei System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
bei System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
bei System.Windows.Threading.DispatcherOperation.Invoke()
bei System.Windows.Threading.Dispatcher.ProcessQueue()
bei System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
bei MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
bei MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
bei System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
bei MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
bei System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
bei MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
bei MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
bei System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
bei System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
bei System.Windows.Threading.Dispatcher.Run()
bei System.Windows.Application.RunDispatcher(Object ignore)
bei System.Windows.Application.RunInternal(Window window)
bei System.Windows.Application.Run(Window window)
bei System.Windows.Application.Run()
bei QualityGate1.App.Main() in C:\Users\Tobias\documents\visual studio 2010\Projects\QualityGate1\QualityGate1\obj\x86\Debug\App.g.cs:Zeile 0.
bei System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
bei System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
bei Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
bei System.Threading.ThreadHelper.ThreadStart_Context(Object state)
bei System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
bei System.Threading.ThreadHelper.ThreadStart()
InnerException:

Ich hoffe ihr könnt mir helfen, meinen Fehler zu finden. Ich habe mehrere Versionen inzwischen probiert, aber ich bekomme jedesmal diese Fehlermeldung.

Ich habe es auch mit einem BackgroundWorker gesehen (hier dann "erstelleMatrix" = doWork-Methode und "berechneVarianz" = workCompleted-Methode), jedoch bleibt die GUI hier hängen und macht nach einer ersten Ausgabe des Depth- und ColorFrame nichts mehr.

Ich bin verzweifelt ob meiner Unfähigkeit dem ganzen Herr zu werden...bitte helft mir, auch wenn die Lösung wahrscheinlich banal ist....

Hier mein Code:

    public partial class MainWindow : Window
    {
        
        BackgroundWorker myBackgroundWorker = new BackgroundWorker();
        ThreadStart myThreadStart;
        Thread myThread;




        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
		//Initialisierungen etc.
                this.image3.Source = this.bitmap;
        }



        void sensor_DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e)
        {
            //myBackgroundWorker.DoWork += new DoWorkEventHandler(myBackgroundWorker_DoWork);
            //myBackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(myBackgroundWorker_RunWorkerCompleted);

            myThreadStart = new ThreadStart(erstelleMatrix);
            myThread = new Thread(myThreadStart);
            using (DepthImageFrame depthFrame = e.OpenDepthImageFrame())
            {
                if (depthFrame != null)
                {
                    depthFrame.CopyDepthImagePixelDataTo(this.depthPixels);
                    /*if (!myBackgroundWorker.IsBusy)
                    {
                        myBackgroundWorker.RunWorkerAsync();
                    }*/
                    if (!myThread.IsAlive)
                    {
                        myThread.Start();
                    }
		    //Ausgabe des Frames
                }
            }
        }

        void erstelleMatrix()
        {
            DepthImagePixel[] depthPixels2 = depthPixels;
            for (int z = 0; z < 480; z++)
            {
                for (int s = 0; s < 640; s++)
                {
                    //Berechnung
                }
            }
            berechneVarianz();
        }



        void berechneVarianz()
        {
            bitmap = new WriteableBitmap(640, 480, 96.0, 96.0, PixelFormats.Bgr32, null);

            for (int i = 0; i < 480; i++)
            {
                for (int j = 0; j < 640; j++)
                {
                    //Berechnung
                }
            }
            image3.Dispatcher.Invoke(  // HIER ERFOLGT DER FEHLER
                System.Windows.Threading.DispatcherPriority.Normal,
                new Action(
                    delegate()
                    {
                        this.bitmap.WritePixels(
                            new Int32Rect(0, 0, this.bitmap.PixelWidth, this.bitmap.PixelHeight),
                            abVarianz,
                            this.bitmap.PixelWidth * 4,
                            0);
                    }
            ));
        }
    }

Hinweis von herbivore vor 10 Jahren

siehe [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke) (analog für WPF)

U
1.688 Beiträge seit 2007
vor 11 Jahren

Hi,

was passiert, wenn Du die Bitmap auch innerhalb des Invoke erzeugst?

114 Beiträge seit 2009
vor 11 Jahren

Hi,

ich versteh da eine Stelle nicht so ganz. Evtl. hat es was mit deinem Problem zu tun.


void berechneVarianz()
{
	// Hier erstellst du aus dem Berechnungs-Thread
	bitmap = new WriteableBitmap(640, 480, 96.0, 96.0, PixelFormats.Bgr32, null);

	for (int i = 0; i < 480; i++)
	{
		for (int j = 0; j < 640; j++)
		{
			//Berechnung
		}
	}
	image3.Dispatcher.Invoke(  // HIER ERFOLGT DER FEHLER ... sicher?
		System.Windows.Threading.DispatcherPriority.Normal,
		new Action(
			delegate()
			{
			
				// Hier greifst du auf Bitmap aus dem Gui-Thread zu.
				// Ist das vielleicht das Problem? 
				this.bitmap.WritePixels(
					new Int32Rect(0, 0, this.bitmap.PixelWidth, this.bitmap.PixelHeight),
					abVarianz,
					this.bitmap.PixelWidth * 4,
					0);
			}
	));
}       

106 Beiträge seit 2011
vor 11 Jahren

Hallo C#-Newbie

Ja, finde den code auch etwas verwirrend.
Ich würde dir empfehlen die Methode "berechneVarianz" in 2 methoden zu splitten.
Die erste berechnet die Varianz und die zweite nimmt das Ergebnis der Berechnung entgegen und setzt das neue Bild.


// Ich verwende hier als Parameter die fiktive Klasse "VarianzResult"
// da ich nicht weiss was du berechnest und wo du das speicherst.
// Tausche VarianzResult dann gegen deine Varianz aus.
private void SetImage(VarianzResult result)
{
	if (image3.Dispatcher.InvokeRequired)
	{
		image3.Dispatcher.Invoke(new Action<VarianzResult>(SetImage), result);
		return;
	}

	// Hier das geänderte Bild setzen.
}

So sollte es klappen.

MfG
Rabban

C
C#-Newbie Themenstarter:in
3 Beiträge seit 2012
vor 11 Jahren

Zu aller erst vielen Dnak für die schnellen Antworten!

Ok, so wie es aussieht habt ihr Recht 😃 jetzt kommt der Fehler nicht mehr, jetzt laggt die GUI immerhin nur noch.

@Rabban: Vielen Dank für den Tipp! Ich schaue im Moment nicht mehr nach der Zerlegung der Methoden, sondern bin froh wenn ich das mit den THreads zum Laufen bekomme. Nach einer Woche verzweifeln hab ich da einfach ein bischen meine Ansprüche heruntergeschraubt...

Aber warum ist das die Lösung? Noch eine Erklärung fände ich gut, dass ich das auch verstehe.
Aus meiner Sicht wurde im UI-Thread die Bitmap zur image.Source gemapped. Danach weise ich der Bitmap in einem anderen Thread eine neue Bitmap zu. Wird dadurch das Mapping gelöst? Oder hat in diesem Fall zwar der UI-Thread Zugriff auf das "Mapping", jedoch nur der externe Thread auf die Bitmap?

vielen Dank,
c#-Newbie

106 Beiträge seit 2011
vor 11 Jahren

Welche Lösung hast du denn jetzt genutzt?

die Zerlegung der Methode ist wichtig fürs Invoke, da "SetImage" sich selber rekursiv aufruft falls ein Invoke benötigt wird.

jetzt laggt die GUI immerhin nur noch.

Was meinst du damit? Blockiert Sie?

MfG
Rabban

F
10.010 Beiträge seit 2004
vor 11 Jahren
C
C#-Newbie Themenstarter:in
3 Beiträge seit 2012
vor 11 Jahren

@ FZelle:
siehe ersten Absatz meines ersten Posts. Ich kam einfach mit anderen Seiten nicht weiter und habe daher hier um Hilfe gebeten.

@ Rabban:
Ich habe nun das erzeugen der Bitmap mit in das Invoke genommen. So funktioniert es. Ich verstehe allerdings nicht genau, worin hier der Unterschied besteht (also ob diese Codezeile außerhalb oder innerhalb ist), da ich dachte dass die Bitmap nichts direkt mit dem Image zu tun hat.

Das mit dem laggen bezeichnet ihr möglicherweise als blockieren. Aber hier such ich erstmal selber weiter. Vielen Dank für die Hilfe!

EDIT:
Meine Lösung ist jetzt folgende (falls später noch jemand den Thread liest):
-Das Mappen in das Invoke mitreinnehmen
-Das Erstellen der Bitmap mitreinnehmen
-Das Invoke in eine eigene Methode packen

        public void aktImage3(byte[] abFramePixels)
        {
            image3.Dispatcher.Invoke(
                System.Windows.Threading.DispatcherPriority.Normal,
                new Action(
                    delegate()
                    {
                        image3.Source = bitmap;
                        bitmap = new WriteableBitmap(640, 480, 96.0, 96.0, PixelFormats.Bgr32, null);
                        this.bitmap.WritePixels(
                            new Int32Rect(0, 0, this.bitmap.PixelWidth, this.bitmap.PixelHeight),
                            abFramePixels,
                            this.bitmap.PixelWidth * 4,
                            0);
                        textBox1.AppendText("1");
                    }
            ));
        }
114 Beiträge seit 2009
vor 11 Jahren

Ich würde sagen:

Wenn du das Bitmap außerhalb der Klammer erstellst wird es im Kontext deines Berechnungs-Threads erstellt. Das heißt bitmap gehört dann nicht zum Gui-Thread. Wenn du aus dem Gui-Thread (per Invoke) den Zugriff versuchst, kann das nicht funktionieren.

Hinweis von gfoidl vor 11 Jahren

Ich habe nun das erzeugen der Bitmap mit in das Invoke genommen. So funktioniert es.

Daher auch [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke) (analog für WPF).

Thema geschlossen