Entwickler-Ecke
Basistechnologien - UI stockt trotz Threading
funcry - Fr 25.02.11 17:25
Titel: UI stockt trotz Threading
Anbei eine etwas allgemeinere Frage, die ich aber gerne noch genauer darstelle.
In einer langsam immer komplexeren Anwendung tritt das Problem auf, dass mein UI trotz Threading manchmal stockt. Dabei fällt es mir schwer herauszufinden was genau den UI Thread so stark belastet.
Der Effekt tritt während folgendem Thread auf:
C#-Quelltext
1: 2: 3: 4:
| Task.Factory.StartNew((status) => foo.Bar(cts), cts.Token, TaskCreationOptions.LongRunning); |
Eine der Möglichkeiten sind natürlich Events welche gefeuert werden. Diese habe ich aber - soweit es möglich ist, eingeschränkt. Genauso sind Datenbankzugriffe weitestgehend innerhalb der Threads abgebildet.
Andererseits vermute ich Methoden innerhalb eines Threads, welche den UI Thread belasten. So vermute ich, dass Random den UI Thread belasten könnte.
Der Effekt, dass der UI Thread belastet ist, leite ich aus der Beobachtung ab, dass die Anwendung manchmal nur zögerlich reagiert, sowie ein progressBar im Modus IsIndeterminate stockt.
Hat jemand vielleicht einen Ansatz oder eine Idee, wie ich das Problem einkreisen kann ? Ein Hinweis auf ein Analyseprogramm welches mir bei der Suche hilft wäre vielleicht ein Ansatz, wobei ich verschiende kostenfreie Programme bereits ausprobiert habe und die Testphasen möglicherweise abgelaufen sind.
Kha - Fr 25.02.11 18:08
funcry hat folgendes geschrieben : |
So vermute ich, dass Random den UI Thread belasten könnte. |
Die Klasse Random :shock: ? Das bezweifle ich. Solange du keine Callbacks im UI-Thread auslöst, wüsste ich von nichts, was die UI so ausbremsen könnte. Ab Ultimate(?) gibt es aber einen eingebauten Profiler, der exakt darstellt, was wann auf welchem Thread ausgeführt wurde.
funcry - Fr 25.02.11 18:40
Hamm ja, aber ich denke zumindes das Abrufen eines Random Wertes führt zu dem genannten Effekt. Ich habe eben eine Anwendung geschrieben welche das Problem zeigt (s. Anhang).
Anbei noch der Quelltext:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93:
| using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using System.Windows;
namespace WpfApplication1 { public partial class MainWindow : Window { private CancellationTokenSource cts;
public MainWindow() { InitializeComponent(); this.progressBar1.IsIndeterminate = true; }
private void button1_Click(object sender, RoutedEventArgs e) { this.cts = new CancellationTokenSource();
Foo foo = new Foo();
Task.Factory.StartNew((status) => foo.Bar(cts), cts.Token, TaskCreationOptions.LongRunning); }
private void button2_Click(object sender, RoutedEventArgs e) { this.cts.Cancel(); } }
public class Foo { #region EventHandler
public event EventHandler<EventArgs> Completed; protected virtual void OnCompleted(EventArgs e) { EventHandler<EventArgs> completed = Completed; if (completed != null) completed(this, e); }
# endregion
public void Bar(CancellationTokenSource cts) { var taskQueueDay = new Queue<Task>(); for (int i = 0; i < 24; i++) { taskQueueDay.Enqueue(Task.Factory.StartNew(() => DoSomething(cts.Token), cts.Token)); }
try { Task.WaitAll(taskQueueDay.ToArray()); } catch (AggregateException) { if (!cts.IsCancellationRequested) cts.Cancel(); } finally { OnCompleted(EventArgs.Empty); } }
private void DoSomething(CancellationToken ct) { ct.ThrowIfCancellationRequested();
while(true) { ct.ThrowIfCancellationRequested(); Random random = new Random(Guid.NewGuid().GetHashCode());
Int64 buffer = 0;
for (int j = 0; j < 10000000; j++ ) buffer += random.Next(100); } } } } |
Hier der XAML Part:
XML-Daten
1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Button Content="Start" Height="47" HorizontalAlignment="Left" Margin="38,83,0,0" Name="button1" VerticalAlignment="Top" Width="207" Click="button1_Click" /> <ProgressBar Height="58" HorizontalAlignment="Left" Margin="38,192,0,0" Name="progressBar1" VerticalAlignment="Top" Width="428" /> <Button Content="Stop" Height="47" HorizontalAlignment="Left" Margin="266,83,0,0" Name="button2" VerticalAlignment="Top" Width="200" Click="button2_Click" /> </Grid> </Window> |
Th69 - Fr 25.02.11 20:45
Hallo funcry,
ich denke, daß einfach deine Thread-Schleife den Prozessor vollständig auslastet, da du ja keine Wartefunktionalität dort drin hast.
Baue einfach mal ein Thread.Sleep(0) oder Thread.Sleep(1) in die Schleife ein...
funcry - Sa 26.02.11 00:52
Das ist interessant.
Sowohl Thread.Sleep(0) als auch Thread.Yield() reduzieren den Effekt. Deutlich spürbar ist, dass das Verhalten der Anwendung weniger stockend wird.
In meine Projekt sind die Ergebnisse, vor allem auch zur Performance, wechselhaft. Habe ich einen Worker-Thread der eine lange Berechnung ausführt, werden ohne den beiden Methoden von Anfang an rund 25 Threads erzeugt. Wenn ich Thread.Sleep() oder Thread.Yield() einsetze, werde anfangs nur 8 Threads erzeugt, nach etwa 1-2 Minuten dann bis zu 20, welche dann abgearbeitet werden bis keine Berechnungen mehr anstehen. Theoretisch möglich wären 30 Threads. Um den Einfluss auf die Performance zu testen, habe ich mit Thread.Sleep() oder Thread.Yield() oft ein besseres Ergebnis, aber nicht immer.
Thread.Sleep(0) lasse ich auf jedenfall drin, danke für den Tipp. Vermutlich sollte ich Visual Studio Ultimate, sofern sich die Möglichkeit ergibt, testen, um mehr Klarheit zu schaffen, ehrlich gesagt sind mir die Zusammenhänge die die Effekte erklären noch nicht vollständig klar.
funcry - Sa 26.02.11 18:56
Abschließend, aufgrund der Tipps, komme ich nun zu folgendem Schluss. Mittels VS-Ultimate erkennt man die Ursachen für die Ruckler. Zum einen der Garbage-Collector. Dieser blockiert den Haupt-Thread und alle Arbeits-Threads an einer Stelle für 4,6 ms. Dieser Effekt tritt zwar auch auf wenn Thread.Sleep(0) vorhanden ist, nur ist die Unterbrechung dann nur 2,5 ms lang. Die Methode Random() ist, wie von Kha erwähnt, unbeteiligt an den Rucklern.
Wenn man jedoch eine speicherlastige Anwendung hat, sind die Verzögerungen viel massiver. Auch erreicht man trotz vieler Threads keine CPU-Auslastung von 100%. Der Haupt-Thread wird so stark belastet, dass die Anwendung massiv ruckelt. Folgende Testmethode provoziert das Problem:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| private void DoSomething(CancellationToken ct) { ct.ThrowIfCancellationRequested();
while (true) { ct.ThrowIfCancellationRequested(); int[] buffer = new int[1000000];
for (int i = 0; i < buffer.Length; i++) buffer[i] = i; } } |
Für diesen Fall habe ich noch keine Lösung gefunden. Dadurch geht auch einiges an Potential bezüglich der Ausführungsgeschwindigkeit verloren, da aufgrund von Arbeizsspeichervorgängen sich die CPUs langweilen.
Für Tipps dazu, und auch ganz allgemein Tipps zu der Art und Weise wie ich Threading in der Test-Anwendung realisiert habe, wäre ich sehr dankbar.
Edit: Hinweis zum ScreenShot:
Dieser zeigt die modifizierte Anwendung mit den speicherlastigen Worker-Threads.
Kha - Sa 26.02.11 19:54
So genau hatte ich mir die VS-Profiler noch gar nicht angeschaut, das sieht ja tatsächlich nützlich aus :mrgreen: :zustimm: .
Aber ist dein zweites Szenario denn realistisch? Du allokierst da ca. 1GB pro Sekunde ;) .
Und dein erstes läuft bei mir komplett flüssig, solange ich einen Kern für die GUI frei lasse. Tue ich das nicht, ruckelt Windows selbst, was ich dann schlecht WPF anlasten kann :) .
funcry - So 27.02.11 12:57
Ja es ist übertrieben, mir ist es gelungen durch Überlegungen den Speicherbedarf in meinem Projekt fast zu halbieren. Für mich ist die Erkenntnis wichtig, da das einiges erklärt was mir so nicht bewusst war.
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!