Benutzer mit den meisten Antworten
GUI friert ein

Frage
-
Hallo,
ich habe ein kleines WinForms Programm geschrieben, basierend auf einer DLL eines Bekannten. Das Programm lädt bestimmte Daten von einer Webseite herunter. Problem ist hierbei, dass mein GUI einfriert, wenn die Daten größer sind. Nachfolgend mein Code:
Private Sub ButtonDownload_Click(sender As Object, e As EventArgs) Handles ButtonDownload.Click Me.ProgressBarStatus.Value = 0 Try If (data IsNot Nothing) Then Dim dataFolder As String = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) Dim dataFile As String = Path.Combine(dataFolder, Me.TextBoxName.Text & data.Extension) Me.LabelDataFolder.Text = dataFile Dim dataDownloader As New DataDownloader(data, dataFile) AddHandler dataDownloader.DownloadProgressChanged, AddressOf dataDownloader_ProgressChanged dataDownloader.Execute() End If Catch wex As System.Net.WebException MessageBox.Show(wex.Message, My.Application.Info.Title, MessageBoxButtons.OK, MessageBoxIcon.Error) Catch ioex As System.IO.IOException MessageBox.Show(ioex.Message, My.Application.Info.Title, MessageBoxButtons.OK, MessageBoxIcon.Error) End Try End Sub
Der Grund hierfür ist die Execute-Methode. Also habe ich die Ausführung in einen anderen Thread verschoben. Mein Code sieht jetzt folgendermassen aus:
Private Async Sub ButtonDownload_Click(sender As Object, e As EventArgs) Handles ButtonDownload.Click Me.ProgressBarStatus.Value = 0 Try If (data IsNot Nothing) Then Dim dataFolder As String = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) Dim dataFile As String = Path.Combine(dataFolder, Me.TextBoxName.Text & data.Extension) Me.LabelDataFolder.Text = dataFile Dim dataDownloader As New DataDownloader(data, dataFile) AddHandler dataDownloader.DownloadProgressChanged, AddressOf dataDownloader_ProgressChanged Await Task.Run(Sub() dataDownloader.Execute()) End If Catch wex As System.Net.WebException MessageBox.Show(wex.Message, My.Application.Info.Title, MessageBoxButtons.OK, MessageBoxIcon.Error) Catch ioex As System.IO.IOException MessageBox.Show(ioex.Message, My.Application.Info.Title, MessageBoxButtons.OK, MessageBoxIcon.Error) End Try End Sub
Ein Problem gelöst, ein anderes verursacht. Das ProgressChanged Ereignis läuft auf dem GUI-Thread während die Execute-Methode auf einem anderen Thread läuft. Das führt bekanntlich zu Konflikten. Ich weiss allerdings nicht, wie ich dieses Problem lösen kann. Habt ihr vielleicht einen eleganten Vorschlag?
Gruss,
LBB
Antworten
-
Hallo Tom,
danke für die Hinweise. Ich habe bei dem Bekannten nochmal nachgehakt und es handelt sich um ein normales Event, d.h. der Eventhandler läuft im gleichen Thread wie die Execute-Methode. Ich habe die ProgressChanged-Methode angepasst und sie sieht nun wie folgt aus:
Delegate Sub SetProgressDelegate(sender As Object, args As ProgressEventArgs) Private Sub dataDownloader_ProgressChanged(sender As Object, args As ProgressEventArgs) If Me.ProgressBarStatus.InvokeRequired Then Dim deleg As New SetProgressDelegate(AddressOf dataDownloader_ProgressChanged) Me.Invoke(deleg, New Object() {sender, args}) Else Me.ProgressBarStatus.Value = CInt(args.ProgressPercentage) End If End Sub
Ist das korrekt? Ich habe mir zumindest für meinen Code folgendes MSDN-Beispiel angeschaut. Jetzt ist noch die Frage offen, wie es mit den Exceptions aussieht, wenn die Execute-Methode auf einem anderen Thread läuft. Da dürfte es auch Konflikte geben, oder?
Gruss,
LBB
- Als Antwort markiert LittleBlueBird Sonntag, 28. Februar 2016 19:28
-
Hallo,
das Invoke brauchst du immer dann nicht wenn alles im selben Thread ablaufen kann. Die MessageBoxen dürften da kein Problem machen, nur wenn du auf die Fenster-UI deines Programms zugreifen willst dürfte es wieder erforderlich werden.
Zu deinen Fragen:
.NET hat 5 Arten von Typen: Klassen, Strukturen, Enumerationen, Schnittstellen und Delegaten
Delegaten sind das für Funktionen, was Schnittstellen für Klassen sind - "Baupläne". Du kannst sie wie ein Objekt mit New instanziieren, da es Objekte sind die dabei entstehen. Auch wenn der Compiler es mittlerweile auch ohne New auf die Reihe bekommt zu erkennen was hinter der Zuweisung steckt.Wie gesagt ist ein Delegat der "Typ" einer Methode. Einer Variablen kannst du damit die Methode selbst per AddressOf mitgeben oder aber eine anonyme Lambda-Funktion wärend der Zuweisung erstellen. In beiden Fällen kannst du danach den Delegaten aufrufen, wobei die dahinter stehende Methode ausgeführt wird.
Tom Lambert - .NET (C#) MVP
Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets- Als Antwort markiert LittleBlueBird Sonntag, 28. Februar 2016 19:28
Alle Antworten
-
Hallo,
dass Threadübergreifende Zugriffe Probleme machen können stimmt. Aber um diese beheben zu können muss man auch das genaue Problem kennen.
Die Frage ist vermutlich wie DataDownloader.DownloadProgressChanged arbeitet. Wenn es ein normales Event ist und entsprechend aufgerufen wird, so läuft der Eventhandler im Thread den du für die Execute-Methode angelegt hast. In dem Fall brauchst du im Eventhandler noch die Invoke-Methode um anschließend auf den UI-Thread zu gelangen und diese zu aktualisieren. Es besteht aber auch die Möglichkeit, was ich anhand deiner Beschreibung vermute, dass der Eventhandler direkt auf den UI-Thread (per Invoke) "gedispatcht" wird. In dem Fall ist die Frage wie das genau geschieht und ob dort dann Probleme auftreten können.
Tom Lambert - .NET (C#) MVP
Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets -
Hallo Tom,
danke für die Hinweise. Ich habe bei dem Bekannten nochmal nachgehakt und es handelt sich um ein normales Event, d.h. der Eventhandler läuft im gleichen Thread wie die Execute-Methode. Ich habe die ProgressChanged-Methode angepasst und sie sieht nun wie folgt aus:
Delegate Sub SetProgressDelegate(sender As Object, args As ProgressEventArgs) Private Sub dataDownloader_ProgressChanged(sender As Object, args As ProgressEventArgs) If Me.ProgressBarStatus.InvokeRequired Then Dim deleg As New SetProgressDelegate(AddressOf dataDownloader_ProgressChanged) Me.Invoke(deleg, New Object() {sender, args}) Else Me.ProgressBarStatus.Value = CInt(args.ProgressPercentage) End If End Sub
Ist das korrekt? Ich habe mir zumindest für meinen Code folgendes MSDN-Beispiel angeschaut. Jetzt ist noch die Frage offen, wie es mit den Exceptions aussieht, wenn die Execute-Methode auf einem anderen Thread läuft. Da dürfte es auch Konflikte geben, oder?
Gruss,
LBB
- Als Antwort markiert LittleBlueBird Sonntag, 28. Februar 2016 19:28
-
Hallo,
so wie du es gezeigt hast kann man das machen. Man könnte auch einen generischen Action-Delegat anstatt dem eigenen Typ nutzen oder einen Lambda-Ausdruck, aber das macht technisch keinen großen Unterschied.
Bei Exceptions ist es so dass diese normalerweise nicht aus anderen Threads heraus abgefangen werden können. Diese kannst du ggf. im Delegaten von Task.Run abfangen. Dann könntest du über Invoke eine Nachricht auf der UI ausgeben. Der Thread selbst würde dann direkt danach abgebrochen werden sofern nichts weiteres in Task.Run kommt.
Tom Lambert - .NET (C#) MVP
Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets -
Hi Tom,
besten Dank für die Antworten und die Tipps.
Zu den Exceptions habe ich ein MSDN-Beispiel gefunden. Hier wird AggregateException verwendet, um die Exception zu Behandeln. Mein Code sieht wie folgt aus:
Try If (data IsNot Nothing) Then ' ... Await Task.Run(Sub() dataDownloader.Execute()) End If Catch ae As AggregateException ae.Handle(Function(ex) If TypeOf (ex) Is System.Net.WebException Then MessageBox.Show(ex.InnerException.Message, My.Application.Info.Title, MessageBoxButtons.OK, MessageBoxIcon.Error) Return True ElseIf TypeOf (ex) Is System.IO.IOException Then MessageBox.Show(ex.InnerException.Message, My.Application.Info.Title, MessageBoxButtons.OK, MessageBoxIcon.Error) Return True Else Return False End If End Function) End Try
Offenbar benötigt man in diesem Fall kein Invoke, um die Nachricht auf der UI auszugeben.
Zu den Delegaten hätte ich noch eine Frage. Mein Code sieht ja so aus:
Delegate Sub SetProgressDelegate(sender As Object, args As ProgressEventArgs) ' ... Dim deleg As New SetProgressDelegate(AddressOf dataDownloader_ProgressChanged)
Wenn ich mir den Action-Delegat anschaue, dann könnte ich meinen Code so umschreiben:
Dim deleg As SetProgressDelegate = AddressOf dataDownloader_ProgressChanged
oder unter Verwendung des Action-Delegaten:
Dim deleg As Action(Of Object, ProgressEventArgs) = AddressOf dataDownloader_ProgressChanged
Die Schreibweise der letzten beiden Code-Zeilen könnte man auch so formulieren: definiere eine Variable vom Typ SetProgressDelegate bzw. Action(Of T1, T2) und weise ihr die Addresse der Methode dataDownloader_ProgressChanged zu.
Die Schreibweise der ersten Codezeile leuchtet mit nicht ein.
1. Wieso muss ich hier New verwenden, schließlich ist es nur eine Methode und nicht eine Klasse?
2. Wieso muss ich die Addresse der Methode dataDownloader_ProgressChanged dem Delegaten als Argument übergeben?
Gruss,
LBB
-
Hallo,
das Invoke brauchst du immer dann nicht wenn alles im selben Thread ablaufen kann. Die MessageBoxen dürften da kein Problem machen, nur wenn du auf die Fenster-UI deines Programms zugreifen willst dürfte es wieder erforderlich werden.
Zu deinen Fragen:
.NET hat 5 Arten von Typen: Klassen, Strukturen, Enumerationen, Schnittstellen und Delegaten
Delegaten sind das für Funktionen, was Schnittstellen für Klassen sind - "Baupläne". Du kannst sie wie ein Objekt mit New instanziieren, da es Objekte sind die dabei entstehen. Auch wenn der Compiler es mittlerweile auch ohne New auf die Reihe bekommt zu erkennen was hinter der Zuweisung steckt.Wie gesagt ist ein Delegat der "Typ" einer Methode. Einer Variablen kannst du damit die Methode selbst per AddressOf mitgeben oder aber eine anonyme Lambda-Funktion wärend der Zuweisung erstellen. In beiden Fällen kannst du danach den Delegaten aufrufen, wobei die dahinter stehende Methode ausgeführt wird.
Tom Lambert - .NET (C#) MVP
Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets- Als Antwort markiert LittleBlueBird Sonntag, 28. Februar 2016 19:28
-
Hallo Tom,
meine Exception-Behandlung funktioniert nicht. Obwohl ich auf AggregateException überprüfe, erhalte ich folgende Fehlermeldung:
System.Net.WebException wurde nicht behandelt.
Hast Du eine Idee, woran es liegen könnte? Wieso funktioniert es im MSDN-Beispiel, bei mir jedoch nicht. Schließlich ist Task.Run nur ein Wrapper für Task.Factory.StartNew.
Gruss,
LBB