Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
Viele Threads eine Datenklasse und ein Deadlock
thinkJD
myCSharp.de - Member



Dabei seit:
Beiträge: 9
Herkunft: Deutschland RLP

Themenstarter:

Viele Threads eine Datenklasse und ein Deadlock

beantworten | zitieren | melden

Hallo Leute,
ich habe ein Problem, mit dem ich nicht weiter komme.

Ich habe mehrere Threads, die alle auf denselben Datenbestand arbeiten.
Dazu habe ich mir eine Datenklasse gebaut, die nur aus den Variablen und Getter sowie setter besteht. Also ähnlich einem Struct nur als Klasse, da ich einen Referenztyp brauche.

In einer Schicht darüber habe ich eine Liste, die eine Anzahl x dieser Datenklassen enthält. Diese Klasse enthält neben der Liste Methoden um der Liste Objekte hinzuzufügen, zu löschen usw.

Jetzt kommt es immer mal wieder zu einem Deadlock und ich habe keinen Schimmer warum.


    public class ZaehlerAufBand
    {
        private clsWasserzaehlerliste m_Liste;
        private log4net.ILog Logger;
        public string PfadVolumenteildatei { get; private set; }


        public ZaehlerAufBand(string PfadVolumenteilDatei)
        {
            log4net.Config.XmlConfigurator.Configure();
            Logger = log4net.LogManager.GetLogger("ZaehlerAufBand");
            m_Liste = new clsWasserzaehlerliste();

            this.PfadVolumenteildatei = PfadVolumenteildatei;
            if (System.IO.File.Exists(PfadVolumenteilDatei)) Deserialisieren(PfadVolumenteilDatei);
        }

        public void addWasserZaehler(WasserZaehler Zaehler)
        {
            m_Liste.Add(Zaehler);
            Logger.Info(string.Format("Volumenteil {0} auf Band hinzugefügt", Zaehler.VT_Fernum));
        }

        public bool removeWasserZaehler(string VT_Fernum)
        {
            if (m_Liste.Count > 0)
            {
                foreach (WasserZaehler tmp in m_Liste)
                {
                    if (string.Equals(tmp.VT_Fernum, VT_Fernum))
                    {
                        m_Liste.Remove(tmp);
                        Logger.Info(string.Format("Volumenteil {0} von Band entfernt", tmp.VT_Fernum));
                        return true;
                    }
                }
            }
            Logger.Error(string.Format("Volumenteil mit der Fertigungsnummer: {0} nicht gefunden", VT_Fernum));
            return false;
        }

        public bool ZaehlerVorhanden(string VT_Fernum)
        {
            if (m_Liste == null) return false;

            foreach (WasserZaehler tmp in m_Liste)
            {
                if (string.Equals(tmp.VT_Fernum, VT_Fernum))
                {
                    Logger.Debug("Zähler ist in Liste vorhanden");
                    return true;
                }
            }
            Logger.Debug("Zähler ist nicht in Liste vorhanden");
            return false;
        }

        public WasserZaehler getZaehler(string VT_Fernum)
        {
            foreach (WasserZaehler tmp in m_Liste)
            {
                if (string.Equals(tmp.VT_Fernum, VT_Fernum))
                {
                    return tmp;
                }
            }

            WasserZaehler temp = new WasserZaehler();
            temp.Dummy = true;
            return temp;
        }

        public int getVolumenteileImSpeicher
        {
            get { return m_Liste.Count; }
        }

        public void Speicher_Loeschen()
        {
            m_Liste.Clear();
            Serealisieren(PfadVolumenteildatei);
        }

Die Klasse war früher ein Singleton, das habe ich aufgrund der beschriebenen Problematik geändert. Eine Besserung gab es trotzdem nicht.
Ich hoffe ihr könnt mir weiterhelfen.
Gruß JD
private Nachricht | Beiträge des Benutzers
rollerfreak2
myCSharp.de - Member

Avatar #avatar-3271.jpg


Dabei seit:
Beiträge: 928

beantworten | zitieren | melden

Hallo thinkJD,

wie synchronisierst du denn deine Listen Zugriffe? Daher zeig mal bisschen Code von der Klasse die deine ZaehlerAufBand Klasse verwendet. Ich befürchte du synchronisiert dort nämlich nix, und daher kommt es zu einem "Deadlock" bzw. 2 Threads greifen "unkoordiniert" auf die Liste deiner Klasse zu.

Außerdem hast du erwähnt das die Klasse Singleton war jetzt aber nicht mehr ist, kannst du das bitte genauer erklären. Daher hat jeder Thread jetzt eine eigene Liste (ZaehlerAufBand) oder nicht?
Again what learned...
private Nachricht | Beiträge des Benutzers
thinkJD
myCSharp.de - Member



Dabei seit:
Beiträge: 9
Herkunft: Deutschland RLP

Themenstarter:

beantworten | zitieren | melden

Stimmt ich synchronisiere nichts :-)
Ich greife einfach zu. Früher hatte ich die Klasse (aus diesem Grund) als Singleton ausgelegt. Dann dachte ich der Deadlock kommt daher und habe die Klasse umgeschrieben. Das Problem ist aber geblieben.

Heute Morgen habe ich meine Versionsverwaltung angeworfen und den alten Zustand wieder hergestellt. Die Klasse aktuell (und mit gleichem Problem) so aus:


   sealed class ZaehlerAufBand : Datenbank.DatenObjekt
    {
        private clsWasserzaehlerliste m_Liste;
        private log4net.ILog Logger;
        public string PfadVolumenteildatei { get; private set; }
        private bool init = false;
        private string VT_Fernum_temp;
        private int ID_Benutzer_temp;
        private clsBackup ProgResultBackup;
        private System.Timers.Timer m_t_Serealisieren;


        static readonly ZaehlerAufBand m_instance = new ZaehlerAufBand();
        public static ZaehlerAufBand Instance
        {
            get { return m_instance; }
        }
        private ZaehlerAufBand() : base() { }

        public bool Initialisieren(INIFile Einstellungen)
        {
            if (this.init) return true;

            try
            {
                this.init = true;
                log4net.Config.XmlConfigurator.Configure();
                Logger = log4net.LogManager.GetLogger("ZaehlerAufBand");
                m_Liste = new clsWasserzaehlerliste();
                this.PfadVolumenteildatei = Einstellungen.getValue("Pfade", "DatenDateiVolumenteile");
                if (System.IO.File.Exists(this.PfadVolumenteildatei)) this.Deserialisieren(Einstellungen.getValue("Pfade", "DatenDateiVolumenteile"));
                return true;
            }

            catch (Exception ex)
            {
                Logger.Error("Initialisieren des ZaehlerAufBand Singletons ist fehlgeschlagen", ex);
                this.init = false;
                return false;
            }
        }
        private void Checkinit()
        {
            if (!(init)) throw new Exception("Das Objekt wurde noch nicht initialisiert");
        }


        public void addWasserZaehler(WasserZaehler Zaehler)
        {
            Checkinit();

            m_Liste.Add(Zaehler);
            Logger.Info(string.Format("Volumenteil {0} auf Band Hinzugefügt", Zaehler.VT_Fernum));
        }

        public bool removeWasserZaehler(string VT_Fernum)
        {
            Checkinit();
            //Logger.Info("Achung! Volumenteile werden nicht gelöscht! Siehe [ZaehlerAufBand]");
            //return true;

            if (m_Liste.Count > 0)
            {
                foreach (WasserZaehler tmp in m_Liste)
                {
                    if (string.Equals(tmp.VT_Fernum, VT_Fernum))
                    {
                        m_Liste.Remove(tmp);
                        Logger.Info(string.Format("Volumenteil {0} von Band entfernt", tmp.VT_Fernum));
                        return true;
                    }
                }
            }
            Logger.Error(string.Format("Volumenteil mit der Fertigungsnummer: {0} nicht gefunden", VT_Fernum));
            return false;
        }

        public bool ZaehlerVorhanden(string VT_Fernum)
        {
            if (m_Liste == null) return false;

            foreach (WasserZaehler tmp in m_Liste)
            {
                if (string.Equals(tmp.VT_Fernum, VT_Fernum))
                {
                    Logger.Debug("Zähler ist in Liste vorhanden");
                    return true;
                }
            }
            Logger.Debug("Zähler ist nicht in Liste vorhanden");
            return false;
        }

        public WasserZaehler getZaehler(string VT_Fernum)
        {
            //Gibt einen Zähler aus der Liste zurück
            foreach (WasserZaehler tmp in m_Liste)
            {
                if (string.Equals(tmp.VT_Fernum, VT_Fernum))
                {
                    return tmp;
                }
            }

            WasserZaehler temp = new WasserZaehler();
            temp.Dummy = true;
            return temp;
        }

     
        public int getVolumenteileImSpeicher
        {
            get { return m_Liste.Count; }
        }
        public void Speicher_Loeschen()
        {
            m_Liste.Clear();
            Serealisieren(PfadVolumenteildatei);
        }
     
        private bool Serealisieren(string Pfad)
        {
            try
            {
                Checkinit();

                if (File.Exists(Pfad)) { File.Delete(Pfad); }

                using (FileStream fstrThis = new FileStream(Pfad, FileMode.CreateNew))
                {
                    XmlSerializer xserThis = new XmlSerializer(typeof(clsWasserzaehlerliste));
                    xserThis.Serialize(fstrThis, m_Liste);
                    fstrThis.Close();
                }
                Logger.Debug("Speichern der Volumenteildaten erfolgreich");
            }
            catch (Exception ex)
            {
                Logger.Error("Wasserzaehlerliste konnte nicht serialisiert werden", ex);
                return false;
            }

            return true;
        }
        private bool Deserialisieren(string Pfad)
        {
            try
            {
                m_Liste = null;

                using (FileStream fstrThis = new FileStream(Pfad, FileMode.Open))
                {
                    XmlSerializer xserThis = new XmlSerializer(typeof(clsWasserzaehlerliste));
                    m_Liste = (clsWasserzaehlerliste)xserThis.Deserialize(fstrThis);
                    fstrThis.Close();
                }

                return true;
            }
            catch (Exception ex)
            {
                Logger.Error("Datendatei konnte nicht geladen werden");
                Logger.Error(ex.Message);
                m_Liste = new clsWasserzaehlerliste();
                return false;
            }

        }
    }

Zugriff erfolgt einfach über die Variable Instance-> Komponenten.ZaehlerAufBand.Instance.ZaehlerVorhanden(VT_Fernum);

Siehst du da ein Problem? Wie könnte ich den Zugriff koordinieren? Es gibt noch eine zusätzliche Anforderung an die Klasse. Die Threads dürfen nicht blocken (es handelt sich um eine Anlagensteuerung) ein Lock fällt also flach denke ich.
Gruß JD
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo thinkJD,
Zitat
Wie könnte ich den Zugriff koordinieren?
da gibt es keine einfache Antwort. Synchronisierung muss man beherrschen, um nicht zu sagen vollständig durchdrungen haben, um halbwegs sichere Aussagen für ein bestimmtes Szenario treffen zu können. Siehe meine Warnungen in SyncQueue <T> - Eine praktische Job-Queue. Eine Antwort in einem Forum ist da nicht ausreichend. Du musst dich selber gründlich in die Materie einarbeiten, insbesondere wenn wie bei einer Prozesssteuerung richtig Geld dranhängt. Alles andere wäre fahrlässig.

herbivore
private Nachricht | Beiträge des Benutzers
thinkJD
myCSharp.de - Member



Dabei seit:
Beiträge: 9
Herkunft: Deutschland RLP

Themenstarter:

beantworten | zitieren | melden

Wenn die Klasse wie im zweiten Beispiel als Singleton ausgelegt ist, kann sie doch eh nur von einem Thread gleichzeitig bearbeitet werden, oder sehe ich das falsch?
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo thinkJD,

das siehst du falsch. Alle Threads teilen sich den gleichen Datenraum. Der Name Singleton bezieht sich auf die Anzahl der Objekte, nicht auf die Anzahl der auf das eine Objekt zugreifenden Threads.

Deine Nachfrage zeigt, wie wichtig es ist, die Zusammenhänge vollständig durchdrungen zu haben. Falsche Annahmen führen zu falschem Code. Nur ist falscher Code in Multi-Threading-Programmen viel schwieriger zu finden ist. Testen alleine hilft da oft leider nicht. Deshalb ist es so wichtig, sauber zu programmieren. Das kann aber aber nur, wenn man genau weiß, was man tut. Deshalb noch einmal mein Rat: Arbeite dich sehr gründlich in das Thema ein.

herbivore
private Nachricht | Beiträge des Benutzers
thinkJD
myCSharp.de - Member



Dabei seit:
Beiträge: 9
Herkunft: Deutschland RLP

Themenstarter:

beantworten | zitieren | melden

Es gibt eine Instanz des Singletons. Da es threadsave ist, kann auch nur ein Thread gleichzeitig etwas damit anstellen. Die anderen werden so lange geblockt. Da die Methoden keine lange Durchlaufzeit haben, ist das zu vernachlässigen.

Wenn ich mir mit getZaehler(); ein Objekt aus der Liste hole, bekomme ich die Referenz zurück. Die kann ich dann in einem anderen Thread munter benutzen und verändern. Das Singleton selbst, bleibt nur während der Laufzeit einer Methode für die anderen Threads geblockt.

Kannst du mir an dem Satatement den Denkfehler aufzeigen?
Gruß JD
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo thinkJD,

wir reden doch über die Klasse ZaehlerAufBand, oder? Diese Klasse ist - ob nun Singleton oder nicht - überhaupt nicht threadsafe. Singletons sind nicht per se threadstafe.

herbivore
private Nachricht | Beiträge des Benutzers
ujr
myCSharp.de - Experte



Dabei seit:
Beiträge: 1770

beantworten | zitieren | melden

Hallo,
Zitat von thinkJD
Die Threads dürfen nicht blocken (es handelt sich um eine Anlagensteuerung) ein Lock fällt also flach denke ich.
Zitat von thinkJD
Die anderen werden so lange geblockt. Da die Methoden keine lange Durchlaufzeit haben, ist das zu vernachlässigen.

Wie nun (bezogen auf lock und blockieren)?

Abgesehen davon zeigt Dein letzter Beitrag leider, dass Du die Problematik tatsächlich überhaupt nicht verstanden hast. Solange Resourcen/Daten gemeinsam genutzt werden, muss der Zugriff darauf geregelt werden.

Um auf die Ausgangsfrage zurückzukommen - warum meinst Du, dass Du einen Deadlock hast? Wie äußert sich dies?

Da in Deinem geposteten Code keine Synchronisationsmechanismen benutzt werden, kann es da nicht zu einem Deadlock kommen. Entweder sitzt also das Problem wo anders oder es ist ein ganz anderes Problem...
private Nachricht | Beiträge des Benutzers