Laden...

Klasse die aus INotifyPropertyChanged erbt, läuft nicht im gleichen Thread

Erstellt von Kostas vor 17 Jahren Letzter Beitrag vor 9 Jahren 3.134 Views
Thema geschlossen
K
Kostas Themenstarter:in
597 Beiträge seit 2005
vor 17 Jahren
Klasse die aus INotifyPropertyChanged erbt, läuft nicht im gleichen Thread

Hallo Zusammen,

Ich habe eine Klasse die vom Interface INotifyPropertyChanged erbt.
Die Instanzen dieser Klasse laufen nicht im Main Thread sondern in einem
eigene. Auf dem Form habe ich ein DataGridView welches gebunden ist auf
ein Dictionary wobei dessen Inhalt die Instanzen der beschrieben Klasse
verwaltet. Jetzt bekomme eine exception:> Fehlermeldung:

"Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement MyGrid erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde."

Hat jemand eine Idee was zu tun ist?
Vermutlich ist es das Zeug mit dem InvokeRequired hab jedoch keine
Ahnung wie ich das mit dem Grid machen soll.

Gruß Kostas

49.485 Beiträge seit 2005
vor 17 Jahren
K
Kostas Themenstarter:in
597 Beiträge seit 2005
vor 17 Jahren

Hallo Zusammen,

dieses Problem wurde mit freundlicher Unterstützung von herbivore gelöst.

Wenn Objekte an ein DataGridView gebunden sind, so dürfen diese nur
im gleichen Thread also im Main thread geändert werden (Wertzuweisung)
Ich hatte jedoch die Aktualisierung der Objekte in einen anderen Thread.
Um das Grid zu aktualisieren ist es notwendig das ChangeEvent an den
Main Thread zu senden. Und das geht so:

        public event PropertyChangedEventHandler PropertyChanged;

        private static SynchronizationContext _sync = new System.Windows.Forms.WindowsFormsSynchronizationContext();

        private void NotifyPropertyChanged(String info)
        {
            _sync.Post(_NotifyPropertyChanged, info);
        }

        public void _NotifyPropertyChanged(Object info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs((String)info));
            }

        }

Gruß Kostas

Dankeschön herbivore.

C
42 Beiträge seit 2014
vor 9 Jahren

Ich habe so ziemlich das selbe Problem aber verstehe die Lösung dafür nicht so ganz...

public class MyEventArgs : EventArgs, INotifyPropertyChanged
{
	public String _Message;
	public MyEventArgs() { }

	public MyEventArgs(String Message)
	{
		this.Message = Message;
	}

	public String Message
	{
		get { return _Message; }
		set
		{
			if (value != _Message)
			{
				_Message = value;
				OnPropertyChanged("Message");
			}
		}
	}

	public event PropertyChangedEventHandler PropertyChanged;
	protected void _OnPropertyChanged(Object propertyName)
	{
		if (PropertyChanged != null)
		{
			PropertyChanged(this, new PropertyChangedEventArgs(Convert.ToString(propertyName)));
		}
	}
	
	public SynchronizationContext _sync = new WindowsFormsSynchronizationContext();
	private void OnPropertyChanged(String info)
	{
		_sync.Post(_OnPropertyChanged, info);
	}
}

Das ist meine verwendete Klasse, diese binde ich in der GUI an einem DataGridView.

DataGridView.DataSource = BindingList<MyEventArgs>

Dennoch wird mir der Fehler geschmissen "Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement...."

Kann mir evtl. wer erklären wieso?

Mit freundlichen Grüßen

4.221 Beiträge seit 2005
vor 9 Jahren

WindowsFormsSynchronizationContext muss natürlich im UI-Thread erstellt werden.. und nicht erst im Thread welcher den Event feuern will...

Überhaupt eine sehr eigenwillige Konstruktion hast Du da gebaut (EventArgs mit INotifyPropertyChange Halloooo ??)

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

C
42 Beiträge seit 2014
vor 9 Jahren

Danke erst einmal für die schnelle Antwort =) -
Ich habe es jedoch auch versucht, wenn ich es in der Main-UI Erstelle doch auch ohne erfolg..

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public class LogData : INotifyPropertyChanged
    {
        public LogData(string text) { Message = text; }
        private string _Message = string.Empty;

        public string Message
        {
            get { return _Message; }
            set{
                if (value != _Message){
                    _Message = value;
                    OnPropertyChanged("Message");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void _OnPropertyChanged(Object propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(Convert.ToString(propertyName)));
        }
        
        protected void OnPropertyChanged(Object propertyName)
        {
            if (PropertyChanged != null)
                Program.MyForm._sync.Post(_OnPropertyChanged, propertyName);
        }
    }

    public partial class Form1 : Form
    {
        public WindowsFormsSynchronizationContext _sync = new WindowsFormsSynchronizationContext();
        public Form1() { InitializeComponent(); }
        private BindingList<LogData> logmessages = new BindingList<LogData>();
        private void button1_Click(object sender, EventArgs e)
        {
            Task.Factory.StartNew(() =>
            {
                logmessages.Add(new LogData("Message"));
            });
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            dataGridView1.DataSource = logmessages;
        }
    }
}

Oder habe ich hier noch einen Fehler gemacht?

PS: Das

EventArgs

muss ich noch entfernen... stammt noch von der alten Lösung.

2.298 Beiträge seit 2010
vor 9 Jahren

Hallo,

zu allererst solltest du die Anpassung rückgängig machen, dass du dein Formular statisch in der Program.cs definierst. Richtiger wäre es, wenn du den SynchronizationContext an das LogData-Objekt übergibst.

Eins noch, die Exception die dir um die Ohren geworfen wird kommt nicht vom den LogData-Einträgen, sondern von der BindingList. - Du fügst in einem anderen Thread (Task) Daten in die Liste ein.

Schau dir mal folgenden Link an, der dürfte dir helfen. WinForms: Multithreaded BindingList

Wissen ist nicht alles. Man muss es auch anwenden können.

PS Fritz!Box API - TR-064 Schnittstelle | PS EventLogManager |

49.485 Beiträge seit 2005
vor 9 Jahren

Hallo CSharpFreak,

generell liegt es gar nicht in der Zuständigkeit der Klasse bzw. des Events, die Ausführung an den GUI-Thread zu delegieren, sondern im Normalfall in der Zuständigkeit des EventHandlers, der den GUI-Zugriff ausführen will.

Im speziellen Fall des DataBinding gilt, dass alle Zugriffe auf die gebundenen Daten aus dem GUI-Thread erfolgen müssen. Dann ist gar kein Delegieren (im EventHandler) mehr nötig bzw. ist es schon vor dem Zugriff erfolgt. Wenn der Zugriff auf die gebundenen Daten nicht aus dem GUI-Thread erfolgt, dann kann mehr schiefgehen, als nur eine Cross-Thread-Exception bei INotifyPropertyChanged. (Vielen Dank an Programmierhans, für den Hinweis, dass man der Verwendung von DataBinding keinen Einfluss auf die Implementierung des verwendeten EventHandlers hat.

Das im ersten Absatz Gesagte wurde in Eleganteste Art aus Worker-Thread auf Controls zugreifen [generell Kontrollfluss zwischen Threads] ausführlich diskutiert, ein Thread, der aus dem genannten FAQ-Beitrag heraus verlinkt ist. Das im zweiten Absatz Gesagte steht direkt im FAQ-Beitrag. Überhaupt steht alles, was du zu dem Thema wissen musst, in der FAQ. Bitte beachte [Hinweis] Wie poste ich richtig? Punkt 1.1 (und 1.1.1).

herbivore

Thema geschlossen