C# Alternative zum Thread Join

roker002

Commander
Registriert
Dez. 2007
Beiträge
2.103
Gibt es eine alternative zum Threadübergreifenden zugriffen auf den speicher?

Ich habe ein Programm geschrieben das gleichzeitig auf viele Tabellen zugreift. Damit der das Hauptfenster nicht durch die zusätzlichen abfragen verlangsamt wird werden die anderen Tabellen mittels einen neuen Thread abgerufen.

Das Problem ist, wenn ich Thread Join mache, dann ist es nichts anderes als wenn ich das ganze sequentiell ablaufen lasse oder habe ich da missverstanden?

Das Problem ist... es werden mehrere Objecte in den neuen Thread erstellt, auf die der Hauptprogramm dann zugreifen soll. Der zugriff erfolgt während neue Objecte von den anderen Thread geschrieben werden.
 
Du kanns paar globale Variablen anlegen, die dann das synchrionisieren.
Code:
int bereit;
Thread1()
{
while(1)
{
Sleep(5);
if(bereit)
{
//...
bereit=0;
}
}
}
Thread2()
{
bereit=1;
}
Oder gleich mit events arbeiten. Windows stellt da Funktionen bereit, wie WaitForSingleEvent oder WaitForMultipleEvent.
Damit kannste es Synchonisieren.
 
Schau dich mal nach Mutexen um, sind zwar schwere Kernelobjekte, aber wenn du sowas unbedingt machen willst, geht das nicht anders.
Oder auch Backgroundworker, respektive Asynchrone Methodenaufrufe, oder wo ich ja viel eher hintendiere sind die Reactive Extension.

Viel Spass
 
es ist auch gedacht, das die zusätzliche befehle im hintergrund ablaufen. Das Problem ist ja das der Main Thread auf den anderen warten muss bis dieser die Daten freigibt.

@Blitzmerker
Sehr Threadunsicher... macht man nur abstürze mit dem Code :D
Man muss schon anmerken dass InvokeRequest gemacht werden sollte ;)

@toeffi
Mutex ist nichts anderes was bei mir der Code Macht.

Das wird im Constructor ausgeführt
Code:
            InitializeComponent();
            try
            {
                this.ReFetch();
                t.Join();
            }
            catch (Exception e)
            {
                ErrorLog.WriteError(e);
            }
Join Blockert alle andere Threads die Parallel zu diesen Laufen bis der "t" beendet ist.. so habe ich es verstanden.

Das Blockieren will ich ja verhindern.

Erzeugt einen Thread der die neue Objecte schreibt
Code:
        public void ReFetch()
        {
            System.Threading.ThreadStart st = new System.Threading.ThreadStart(this.Fetch);
            t = new System.Threading.Thread(st);
            t.Start();
        }

hmm kann mich da jemand korregieren oder habe ich alles richtig gemacht?
 
Ok dann würde ich dir zu den Asynchronen Methoden raten. Da du nicht mehr preisgibst als das du nur im Hintergrund auf viele Tabellen(denke mal eine Datenbank) zugreifst, wirst du wohl eher nur lesend zugreifen richtig? Ansonsten erzähl uns einfach mal mehr.
Wenn ja, dann interessieren dich ja nur die Rückgabewerte und somit könnten die Asynchronen Methoden für dich interessant sein.
Andere Frage wie realisierst du den Zugriff auf die Tabellen? Rudimentäre Sql(OleDB,ODBC)DataReader oder LINQ to SQL oder ADO.NET EF?
 
Thread sind da eine heiße Sache, die viel Gutes für dich machen können, aber auch sehr komplex sind. Hier gehts nicht nur darum, das die Objekte im Hintergrund magisches machen, sondern das du das Ganze auch gescheit synchronisierst. Thema "Race Conditions": Was passiert wenn 2 Threads gleichzeitig auf das selbe Objekt zu greifen? Forms sind nicht threadsave, d.h. das setzen von Werten in der Form muss stets im Hauptthread erfolgen. Über Invoke bzw. InvokeRequired kann man es handeln. Zur Synchronisierung kann man die System.Threading.Monitor Klasse verwenden, gerade bei kritischen Bereichen zwingend erforderlich (Vermeidung von Race Conditions). Diese Monitor Klasse wäre eine "leichte" Variante eines Semaphores welches nur für den Prozess gilt. Mutex wäre da schon Prozessübergreifend (mit entsprechend mehr Drumherum in den .Net Framework/OS Internas). Je nach dem was du genau machen willst, kannst du dich ja für die eine oder andere Variante entscheiden. Allgemein wirst du aber auch schon viel mit den Asynchronen Methoden "reisen" können. Also gib mal mehr Input, wie toeffi schon schrieb...

Mit Thread.Join würde ich nicht mehr anfangen wollen, da du stets vorher prüfen musst in welchem State dieser Thread ist. Bei nicht gestarteten Threads gibts eine Exception. Ebenso ist das Prüfen ob der Thread gestartet wurde und anschließendes Join auch wieder eine Race Condition die du nur schwer erkennen bzw. fixen kannst ohne Verwendung von Synchronisierungsobjekten ala Mutex, Semaphore etc. Ebenso ist nicht jeder "Einzeiler" auch gleichbedeutend mit Atomic-Statements. Will sagen, ein simples
Code:
i++;
wird da schon mal mit 3 Befehlen ausgeführt (Wert der Variable laden, Wert um 1 erhöhen und neuen Wert wieder zurück in die Variable schreiben). Das ist bei mehreren Threads dann auch schon eine Race Condition wenn diese Threads auf i zugreifen. In welchem Status ist da die Variable? Schwer bzw. unmöglich zu debuggen und mysteriöse Effekte sind keine Seltenheit.
 
Zuletzt bearbeitet:
@rossibär
ja Betriebssysteme vorlesung war schon ne weile her her :P

also ich habe ein Hauptform.... der läd aus der Haupttabelle die daten zum darstellen der Tabelle. Da diese tabelle übergreifende mehrzeilige einträge hat, muss ich die anderen tabellen auch laden.
Pro Zeile die ausgelesen wird, benutze ich einen GroupBox, 3 Labels und 3 TextBoxen.
Alle diese Daten sind nur zum anschauen da, d.h. ich muss mir keine sorge um die Update/Delete/Insert kümmern.

Ich habe nur einen Prozess und nicht mehrere. Der neuer Thread liegt auch in eine ganz andere Klasse, die erst von der mainklasse aufgerufen wird und beim aufruf das auslesen der Tabellen initialisiert.

ooh je wenn ich genau nachdenke... ich habe noch eine sache nicht getestet was sicher eine Exception hervorufen wird. Das ganze was beschrieben worden war geschieht ja beim initialisieren des Forms vom Hauptthread. wenn ich während der laufzeit nochmal auf die externe klasse zugreife und die Controls leere, gibt es sicher eine exception. Naja es geht nicht ohne InvokeRequest. Ich dachte man könnte die Threads irgendwie mergen... ohne großes drumherum.


EDIT

Ich habe den Code so geändert dass es mit InvokeRequired aufgerufen wird.... nur weiss ich nicht ob es so funktionieren wird.

Code:
        public Class1()
        {
            InitializeComponent();
            try
            {
                this.ReFetch();
                t.Join();
            }
            catch (Exception e)
            {
                ErrorLog.WriteError(e);
            }
        }
        /// <summary>
        /// Erzeugt einen eigenen thread das die Visuelle ergebnisse erneuert
        /// </summary>
        public void ReFetch()
        {
            System.Threading.ThreadStart st = new System.Threading.ThreadStart(this.Invoking);
            t = new System.Threading.Thread(st);
            t.Priority = System.Threading.ThreadPriority.Highest;
            t.Start();
        }
        /// <summary>
        /// Schreibt neue Information und stellt diese visual dar.
        /// </summary>
        private void Fetch(Control c)
        {
            if (Krankenkasse.ForeignKey == 0)
                return;
            DataSet ds = new DataSet("ds");
            DataBase.DbDataToDataSet(ref ds, _Select, "ds");
            
            Control RichText = new Control();
            Control[] cs = c.Controls.Find("richTextBox1", true);
            if (cs.Length > 0)
                RichText = cs[0];
            RichText.Text = string.Empty;

            foreach (DataRow row in ds.Tables[0].Rows)
            {
                ///Tu Irgendwas
            }
        }
        /// <summary>
        /// Zum modifizieren von Control gedacht.
        /// </summary>
        /// <param name="c"></param>
        delegate void ModifyControl(Control c);
        /// <summary>
        /// Erzeugt die Modifizierung der Controls.
        /// </summary>
        /// <param name="c"></param>
        private void ModifyingControl(Control c)
        {
            Fetch(c);
        }
        private void Invoking()
        {
            if (this.InvokeRequired)
            {
                ModifyControl c = new ModifyControl(ModifyingControl);
                this.Invoke(c, new Object[] { this });
            }
            else
                ModifyingControl(this);
        }

Kann ich wirklich Invoking als neuer Thread aufrufen? Ich habe noch nicht ausprobiert, aber irgendwie habe ich eine vorahnung das da alles schief gehen wird, sobald ich den t.Join() raushaue
 
Zuletzt bearbeitet:
Um ehrlich zu sein, blicke ich noch nicht durch deine Invoking Sache ganz durch. Mein Bauch sagt, dass das so nicht gehen kann. Ich würde es eher so machen:

Code:
delegate void JumpToMainThread(Control c);
private void MainThreadAction(Control c)
{
  if(this.InvokeRequired)
  {
    JumpToMainThread jumper = new JumpToMainThread(MainThreadAction);
    jumper.Invoke(this, new object[]{ c });
    return;
  }
  ModifyControl(c);
}

private void ModifyControl(Control c)
{
  // mach was du willst
}

Die Sache ist die, dass das Invoke u.U. nicht sofort beim ersten Mal im richtigen Thread landet. Somit muss man immer weiter Invoke ausführen, bis der eigentliche Hauptthread getroffen wird. In deinem Beispiel würde ich es aber so lesen, das du nur 1x Invoke aufrufst und dann deine Aktionen in dem entsprechenden Thread ausführst, was natürlich bei mehreren Threads gnadenlos schief gehen wird.

Zum Thema Thread und Join würde ich auch etwas anderes machen.

Code:
private readonly object synchronizer = new object();

private void MyThread()
{
     System.Threading.Monitor.Enter(synchronizer);
     //
     // mach was du willst (ist sowieso threadsafe)
     //
     System.Threading.Monitor.Exit(synchronizer);
}

private void OnLoad(EventArgs e)
{
   System.Threading.Monitor.Enter(synchronizer);
   //
   // mach auch was du willst (ist sowieso threadsafe)
   //
   System.Threading.Monitor.Exit(synchronizer);
}

Das Ganze hat dann folgendes Verhalten, wenn der Thread VOR dem OnLoad gestartet wurde, dann wartet OnLoad solange bis der Thread seine Arbeit getan hat. Wenn der Thread während OnLoad gestartet wird, dann wartet er bis OnLoad fertig ist. Wenn der Thread nach OnLoad gestartet wurde, ist es eh unwichtig, da die beiden nicht mehr kollidieren können. Die Monitor Klasse benötigt immer ein Objekt anhand dessen es erkennen kann ob gewartet werden muss oder nicht. Um den Overhead klein zu halten wird hier die Superdupper-Basis-Klasse Object verwendet. Das readonly in der Deklaration von synchronizer setze ich um sicherzustellen, das ich nicht auf die Idee komme jemals dieses Objekt durch ein anderes Objekt zu ersetzen, weil dann die Synchronisierung hinfällig ist.

Viel Erfolg
Rossibaer

EDIT: Mir fiel gerade noch ein, dass du die Sachen zwischen den Monitor.Enter bzw Exit-Aufrufen relativ kurz und bündig halten solltest um nicht unnötig lange Wartezeiten der Threads zu provozieren. Theoretisch ist es egal, jedoch macht dann das Threading auch wenig Sinn, wenn der eine Thread ewig lang auf die Freigabe durch den anderen Thread wartet. Da kannst du ja auch gleich in einem Thread bleiben. Ebenso sind lokale Variablen in der Threadmethode deine wahren Freunde und der Zugriff auf Klassen- bzw. Objektvariablen eher wenig oder selten zu verwenden. Das wird dir ne Menge Kopfzerbrechen sparen.

EDIT2: Zu deinem Code: du erzeugst zuerst einen Refetch-Thread mit hoher Priorität, der Invoke aufruft? Unmittelbar nachdem der Thread gestartet wurde, wartet dein Hauptthread mit Join() darauf, das der Refetch-Thread seine Arbeit abgeschlossen hat? Ebenso macht dein Invoking (ReFetch-Thread)seinerseits wieder einen Rücksprung in den Hauptthread über InvokeRequired und Invoke. Kommts da nicht zum Deadlock? Ich hätte jetzt eher gedacht das du den Refetch-Thread anstößt der dein Resultset füllt, dann parallel weiter mit anderen Init-Sachen im Hauptthread deine Hauptmaske beackerst und zum Schluß mit Join wartest bis das Refetch fertig ist um dann abschließend noch paar Updates der Maske/Controls im Hauptthread nachzuführen.
 
Zuletzt bearbeitet: (Je später der Abend umso klarer die Gedanken ;))
@rossibär

Ja es kommt wahrscheinlich zum deadlock... zumindest sehe ich das wenn ich das projekt kompiliere und anzeigen lasse. Dann kommt ein Fehler von der VS.

Wie oben gesagt... ich hatte noch keine Zeit den Code auszuprobieren, bis vor kurzem.

Ein anders Problem ist... ich will ja in die Hauptklasse die neue Objekte schreiben. Wenn ich es als Control weitergebe gebe ich nur die Kopie von der Klasse ( Soweit ich mich nicht irre). Ich will ja mit Origenal arbeiten und brauche deswegen "ref". ref kann man aber auf THIS nicht anwenden. Wie kann ich den sonst einen Element zu der Klasse hinzufügen ohne mit der Kopie zu arbeiten.....


Hmm naja... ich werde das wohl in die Optimierung verschieben müssen. Das Problem ist nicht in eine stunde zu lösen, da ich zwischen 2 Klassen hin und her wechsele.... Zusätzlicher zugriff auf die Static Varible... was wiederum zum race condition führen kann.

Ich lasse es in einen Thread machen.... hoffentlich kriege ich da keine lags beim programm.
 
Verweistypen (wie z.B. Controls oder einfach nur als "class" definierte Typen) werden nicht kopiert, wenn du sie an eine Methode als Parameter weiter reichst. Somit ist es hinfällig ob der Parameter mit oder ohne "ref" festgelegt wurde. Es wird stets die Referenz auf das Objekt übergeben. Wenn jetzt jedoch deine Methode innerhalb das Objekt neu erzeugt und somit eine neue Referenz nach aussen liefern will, dann muss der Parameter mit ref markiert werden, da sonst die Methode nur intern das neue Objekt erzeugt und diese neue Referenz nicht nach aussen liefert (alternativ kannst du auch "out" dafür verwenden, wobei dann die Methode eine Objektreferenz nach außen liefern muss. Anders sieht es mit Wertetypen aus, diese werden bei einem Parameter ohne ref stets kopiert. Einige Vertreter dieser Gattung wären Integer, Double, DateTime, Byte etc. Sprich die "nativen" Typen des .Net Frameworks. Eine gewisse Sonderstellung hat der Typ String. An sich ist String eine Klasse (somit ein Verweistyp), der sich aber durch überladene Operatoren/Methoden in einigen Bereichen wie ein Wertetyp verhält, z.B. Vergleichsoperatoren, indem nicht die Referenz des String-Objektes, sondern der Wert des String-Objektes verglichen wird.

Desweiteren meine ich mich zu erinnern, das die Controls alle im selben Thread wie die Form selbst erzeugt werden müssen. Dabei ist es egal ob die Form im Hauptthread oder einem neuen Thread erzeugt wurde. Sobald aber ein Control eines Threads zu einer Form eines anderen Threads hinzugefügt werden soll, wird dich das .Net Framework daran hindern und eine schöne runde Exception auslösen (Stand .Net 2.0). Bei den neueren Versionen ala 3.x und 4 kann ich dir nicht genau sagen, ob Exceptions ausgelöst werden oder nicht.

Aber genug geschrieben, sonst gleitet das noch zu sehr ins Detail ab...

Viele Grüße
Rossibaer
 
Zuletzt bearbeitet:
ich meine schon dass dieses Control nach außen weitergereicht werden soll... hmm bin jetzt gerade verwirrt, da ich es aus C und C++ anders kenne. Da C# kein ref und de-ref hat (naja ist ja so ählich mit out und ref, verweis auf die speicherstelle oder den wert im speicher.) kann man ja nicht sagen dachte ich dass Control Typen nur weiterkopiert werden und man auf die Kopie des Objects was neues schreibt. Aber wenn die Controls als ref typ weitergegeben werden... ich kann ja nicht schreiben
Code:
void Bla(ref Control t){}
Control Bla(Control t){return t;}
public init ()
{
    Bla(ref this);   //THIS is schreibgeschützt und kann daher nicht als ref oder out weitergegeben werden
    this = Bla(this); //Geht auch nicht.. weil es zum gleichen fehler wie oben führt.
}

Wenn ich so verstanden habe, dann kann man auch so machen.

Code:
void Bla(Control c)
{
    Label l = new Label();
    c.Controls.add(l);
}

public init()
{
    Bla(this); //ist Label l nach außen Sichtbar? kann ich auf dem Label l zugreifen?
}

Ich musste noch nie einen "THIS" weitergeben deswegen hat mir auch ref Control gereicht, da ich auf jeden Fall wusste ich kriege alles nach außen gezeigt. Wird im 2ten Beispiel this wirklich nicht einfach kopiert?
 
this ist nun mal der Zeiger auf das Objekt der Klasse in deren Kontext du dich gerade befindest. Das kann alles mögliche sein. Es wird lediglich die "Referenz" kopiert bei der Übergabe. Das Objekt auf welches diese Referenz zeigt ist dabei das selbe. Dein 2. Code wird funktionieren. Label 1 ist dann über die ControlsCollection deines Parent-Objektes erreichbar und sichtbar. D.h. entweder du ermittelst das Label über die ControlsCollection deines Parent-Controls ala "Label label1 = this.Controls(0);" oder du gibst die Referenz auf Label 1 über einen out Parameter nach außen ala "void Bla(Control c, out Label label){};" oder du gibst die Referenz auf Label 1 über den Return-Wert zurück ala "Label Bla(Control c){};". Wenn ich es mir recht überlege, brauchst du die Referenz this nicht an die Methode weitergeben, wenn diese in der selben Klasse ist.
 
Naja wenn ich den Label in einen anderen Thread erzeuge muss ich ja über invoke mitgeben... es geht dann halt nicht anders... aber klar wenn ich den weitergegeben Control neu erzeuge ala new Control dann hat man keine referenz mehr auf dem alten Objekt.

Hmm ich bleibe dennoch beim alten Code und lasse den Control per ref weitergeben... ist mir sicherer als später stundenlang "eventuell" nach fehlern zu suchen, weil es auf einmal nicht mehr funktioniert
 
Wenn der andere Thread das Control erzeugt ist alles erstmal in Ordnung bis du dann das Control im Hauptthread der Form zu ordnest. Es wird sicher eine Exception geben. Controls müssen, soweit ich weiß, im selben Thread wie die Form erstellt werden, damit man sie der Form auch hinzufügen kann. Per Ref solltest du das Control wieder nach außen liefern, sonst ist das neue Control außerhalb der Methode nicht direkt "greifbar". Wird ja schließlich eine neues Objekt mit neuer Referenz erzeugt.

Viel Erfolg!
Rossibaer
 
Zurück
Oben