Laden...

Thread-Problem bei DataAdapter.Fill() + DataRowChangeEventHandler

Erstellt von Alex71 vor 18 Jahren Letzter Beitrag vor 18 Jahren 4.288 Views
A
Alex71 Themenstarter:in
77 Beiträge seit 2005
vor 18 Jahren
Thread-Problem bei DataAdapter.Fill() + DataRowChangeEventHandler

Hallo!
Ich habe das Holen von Daten aus einer MySql-DB in einen extra Thread ausgelagert, damit das UI noch reagiert (es werden bei meinem Test etwa 15.000 Datensätze importiert). Ungefähr so:


class myClass: System.Windows.Forms.Form
{
public void importData()
    {			
       myDataAdapter.Fill(myDataSet.Tables[0]);
    }
private void myFunction()
   {
      myDataSet.Tables[0].RowChanged += new DataRowChangeEventHandler(rowChanged);
      ThreadStart importDataDelegate = new ThreadStart(importData);
      Thread myThread = new Thread(importDataDelegate);
      myThread.Start();										
      while(myThread.IsAlive)
      {			
		Application.DoEvents();
		Thread.Sleep(1); //damit CPU-Last nicht auf 100% steigt
      }
   }
private void rowChanged(object sender, DataRowChangeEventArgs e)
   {
	if(e.Row.RowState == DataRowState.Added)
	{				
		myProgressBar.PerformStep();				
	}
   }
}				
 

Nun ist es ganz offensichtlich so, daß myDataAdapter.Fill() in myThread durch den Hauptthread blockiert wird.
myDataAdapter.Fill() braucht für die 15000 Datensätze normalerweise (ohne Extra-Thread) etwa 30 sek. Bei der oben beschriebenen Lösung sind es bereits ca. 40 sek. Ändere ich den Parameter bei Thread.Sleep() auf 5ms, dauert da ganze bereits über 2 minuten..Bei 100ms hab ich das Programm schließlich abgebrochen, hätte vermutlich ne halbe Stunde gedauert.
Nun frage ich mich, warum ist das so und wie könnte ich das besser lösen?

A
Alex71 Themenstarter:in
77 Beiträge seit 2005
vor 18 Jahren

*schieb*

4.221 Beiträge seit 2005
vor 18 Jahren

Mit Thread.Sleep legst Du den UI-Thread schlafen....

Du aktualisierst aber laufend eine Progressbar.... und wo denkst Du das diese läuft ? Natürlich im UI-Thread (und den legst Du ja andauernd schlafen)

Ist übrigens der Zugriff in PerformStep in den UI-Thread gemarshalled ?? Sonst machts früher oder später mal Krawumm

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

A
Alex71 Themenstarter:in
77 Beiträge seit 2005
vor 18 Jahren

Sry, aber ich stehe völlig auf'm Schlauch.Ich weiss gar nicht, wo ich anfangen soll..

Mit Thread.Sleep legst Du den UI-Thread schlafen....

Richtig, da sonst die CPU anfängt zu glühen. Wie sonst kann ich das verhindern?

Du aktualisierst aber laufend eine Progressbar.... und wo denkst Du das diese läuft ? Natürlich im UI-Thread (und den legst Du ja andauernd schlafen)

Daß das alles andere als optimal ist, hatte ich mir gedacht. Erwartet hatte ich allerdings einen gewissen "Stau" bei der Ereignisbehandlung, nicht jedoch eine Ausbremsung des Threads, welcher die Daten von der DB holt. Ich verstehe nicht, warum myThread umso länger braucht, je länger der UI-Thread mit Thread.Sleep() unterbrochen wird. Müßte es nicht theoretisch sogar umgekehrt sein, da myThread ja mehr Rechenzeit bekommt?

Ist übrigens der Zugriff in PerformStep in den UI-Thread gemarshalled ??

Nein, habe von Marshalling noch weniger Ahnung als von Threading..Erinnere mich dunkel, im Zusammenhang mit verteilten Anwendungen mal was von Marshalling gehört zu haben und dachte eigtl, daß man diese Technik nur in den seltensten Fällen braucht. Aber das Problem mit nicht mehr reagierenden UI's ist hingegen doch ein recht alltägliches?

Da offensichtlich mein ganzer Ansatz was für den Papierkorb ist, wäre ich wirklich sehr froh über ein Code-Beispiel.

4.221 Beiträge seit 2005
vor 18 Jahren

Vergiss den Thread .... wirf die Progressbar weg und das Teil fliegt.

PS: Ich selber tappe ja heute noch von Zeit zu Zeit in Threading-Fallen.... also kann ich jedem Anfänger nur empfehlen: Hände weg von Threads (ausser Du weisst genau was du tust).

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

G
130 Beiträge seit 2005
vor 18 Jahren

hey

@Programmierhans

Vergiss den Thread .... wirf die Progressbar weg und das Teil fliegt.

PS: Ich selber tappe ja heute noch von Zeit zu Zeit in Threading-Fallen.... also kann ich jedem Anfänger nur empfehlen: Hände weg von Threads (ausser Du weisst genau was du tust).

Aber wie realisierst du dann eine saubere Programmstruktur, wo aus dem UI-Thread heraus ein blockierender Methodenaufruf gestartet wird. (z.B. eine Datenbankabfrage mit einem TimeOut von 30Sek) Kannst du keine Verbindung zur DB aufbauen, ist der UI-Thread für 30Sek blockiert. Nervöse Anwender neigen dann zur Meinung, dass das Programm abgestürzt ist und schließen dann z.B. über den x-Button in der ControlBox die Anwendung. Dies führt dann zwangsläufig zu der Meldung, dass das Programm nicht mehr reagiert.
Wie kann man sowas handlen???

greets george

F
10.010 Beiträge seit 2004
vor 18 Jahren

Es ist wie Programmierhans schon sagte etwas komplexer.
Beim FW 2.0 hat MS den BackgroudWorker eingeführt, der Dir enorm
bei soetwas hilft.

Da gibt es eine definierte Schnittstelle zu deinem Mainthread, zur ausgabe von Daten.

Aber schau Dir mal diese Lösungen an:
http://www.codeproject.com/csharp/invoke_other_way.asp
http://www.codeproject.com/csharp/ThreadHelper.asp
http://www.codeproject.com/csharp/threadsafeforms.asp

A
Alex71 Themenstarter:in
77 Beiträge seit 2005
vor 18 Jahren

Hm, also wäre es folgendermaßen unbedenklich?


private void rowChanged(object sender, DataRowChangeEventArgs e)
		{
			if(InvokeRequired)
			{
				//IAsyncResult result = BeginInvoke(new DataRowChangeEventHandler(rowChanged),new object[]{sender,e});	
				//EndInvoke(result);
				Invoke(new DataRowChangeEventHandler(rowChanged),new object[]{sender,e});				}
			else
			{
				if(e.Row.RowState == DataRowState.Added)
				{
					progressBar1.PerformStep();						
				}
			}
		}
 

Wobei sich mir aber immer noch die Frage stellt, warum durch Thread.Sleep() beim UI-Thread auch der Arbeitsthread länger braucht? Der Unterschied ist bei 1ms als Parameter jedoch minimal, ich könnte eigtl. damit leben..Wobei, ich sollte das wohl auch nochmal auf ner Oldtimer-Kiste testen, vielleicht ist der Unterschied dann ja nicht mehr akzeptabel..Auf jeden Fall würde ich diesen Punkt gerne verstehen!

Denn ich mag mich nicht damit abfinden, daß ich entweder 100% CPU-Last oder eine (wenn auch minimale) Verlangsamung des Programmablaufes in Kauf nehmen muß.

F
10.010 Beiträge seit 2004
vor 18 Jahren

Ich frage mich immer, warum ein PC nicht zu 100% ausgelastet sein darf.

Wenn Deine GUI doch weiter benutzbar ist, sei doch froh, wenn die
Routine mit voller Kraft arbeitet.

Erst wenn dadurch irgendetwas nicht mehr geht macht es sinn dies zu ändern.

Und ja, so ist rowChanged zumindest Threadsave.

A
154 Beiträge seit 2005
vor 18 Jahren

bin mir nicht sicher wie der RowChangeEventHandler aufgerufen wird. Async oder Syncron. Denke mal das die Zeit für den Rücksprung verloren geht, da du deine Gui schlafen legst. Versuche mal zum Marshallen Control.BeginInvoke(...) zu verwenden.

.

A
Alex71 Themenstarter:in
77 Beiträge seit 2005
vor 18 Jahren

Danke Euch Allen!
Invoke oder BeginInvoke macht hier keinen messbaren Unterschied.
Und das mit Thread.Sleep(1) werde ich wohl beibehalten. Eine größerer Datentransfer kann schonmal 1-2 Minuten andauern und ich bin schon der Meinung, daß die CPU sich in der Zwischenzeit mit anderen Tasks sinnvoller beschäftigen könnte.
Hab das jetzt auf mehreren Rechnern ausgiebig getestet, eine nenneswerte Ausbremsung ist erst bei Parametern von etwa 200ms aufwärts festzustellen.

D
155 Beiträge seit 2005
vor 18 Jahren

Also zum Fillen:

Woher weißt du eigentlich wie viele Datensätze aus der Datenbank kommen (SELECT COUNT(*))?

Ne, ich kann ja verstehen, wenn das Projekt UNBEDINGT eine Progressbar beim füllen braucht, aber reicht nicht einfache eine Animation? Das ist mit 2 Fäden leichter zu bewerkstelligen, als MT und Events zusammen zu klatschen. Da verliert man sowieso sehr schnell den Überblick.

A
Alex71 Themenstarter:in
77 Beiträge seit 2005
vor 18 Jahren

Ja klar, vorher geht natürlich ein SELECT COUNT(*) an die DB um die progressBar1.Maximum property zu setzen.

So ganz wohl fühle ich mich auch nicht dabei, in einem Thread Events zu feuern, die im temporär schlafenden UI-Thread behandelt werden..Auch wenn es wie gesagt bei Thread.Sleep(1) wie gewünscht funktioniert, scheint es mir doch ne recht wacklige Sache zu sein.

Beispiel:
Wenn ich statt der Schleife while(myThread.IsALive) einfach nen myThread.Join() ausprobiere, tut sich nämlich gar nichts mehr (Deadlock?).

Eine Frage hätte ich noch:
Wenn ich im Nicht-UI-Thread eine Exception behandeln und die Fehlermeldung in einer MessageBox anzeigen möchte, muß diese wohl auch per Invoke aufgerufen werden, oder?