Announcement

Collapse
No announcement yet.

Frage zu Multi-Threading

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Frage zu Multi-Threading

    Hallo Leute,

    nachdem ich mich im Internet in das Thema "Multi-Threading in VB.NET" eingelesen habe, dachte ich mir, erstelle ich mal ein kleines Testprogramm, um zu schauen, wie das funktioniert. Das Testprogramm soll einfach nur ein Formular mit zwei Listboxen öffnen, zwei Threads sollen in je einer der Listboxen Messages ausgeben (und damit lang laufende parallele Verarbeitungen simulieren, die Fortschrittsmeldungen bringen) und nachdem beide Threads abgearbeitet sind, soll eine Messagebox erscheinen, die das Ergebnis beider Threads anzeigt.

    Dazu habe ich eine Testklasse erstellt, die die lang laufende Verarbeitung simuliert:
    Code:
    Imports System.Threading
    
    Public Class TestClass
    
      Public Event ShowProgress(ByVal message As String)
    
      Private _milliSeconds As UShort
    
      Public Sub New(milliSeconds As UShort)
        _milliSeconds = milliSeconds
      End Sub
    
      Public Function Run() As UShort
        For i As Integer = 1 To 20
          RaiseEvent ShowProgress("Run " & i)
          Thread.Sleep(_milliSeconds)
        Next i
    
        Return _milliSeconds
      End Function
    
    End Class
    Das Formular mit den zwei Listboxen hat folgenden Code. Die zwei Prozeduren entsprechen sich, sprechen halt nur jeweils die andere Listbox an:
    Code:
    Public Delegate Sub ShowProgressDelegate(ByVal message As String)
    
    Public Class Form1
    
      Public Sub AddMessage1(ByVal message As String)
        If String.IsNullOrEmpty(message) Then
          Exit Sub
        End If
    
        If Me.InvokeRequired Then
          Me.Invoke(New ShowProgressDelegate(AddressOf Me.AddMessage1), New Object() {message})
        Else
          Me.ListBox1.Items.Add(message)
          Me.ListBox1.SelectedIndex = Me.ListBox1.Items.Count - 1
          Application.DoEvents()
        End If
      End Sub
    
      Public Sub AddMessage2(ByVal message As String)
        If String.IsNullOrEmpty(message) Then
          Exit Sub
        End If
    
        If Me.InvokeRequired Then
          Me.Invoke(New ShowProgressDelegate(AddressOf Me.AddMessage2), New Object() {message})
        Else
          Me.ListBox2.Items.Add(message)
          Me.ListBox2.SelectedIndex = Me.ListBox2.Items.Count - 1
          Application.DoEvents()
        End If
      End Sub
    
    End Class
    Und schließlich und endlich der Code, der beides ansteuert:
    Code:
    Imports System.Threading.Tasks
    
    Public Class Start
      Private Const MULTI_THREAD As Boolean = False
    
      Public Shared Sub Main()
        Dim test1 As TestClass
        Dim test2 As TestClass
        Dim task1 As Task(Of UShort)
        Dim task2 As Task(Of UShort)
        Dim result1 As UShort
        Dim result2 As UShort
    
        test1 = New TestClass(50)
        test2 = New TestClass(100)
    
        Using frm As Form1 = New Form1
          frm.Show()
    
          AddHandler test1.ShowProgress, AddressOf frm.AddMessage1
          AddHandler test2.ShowProgress, AddressOf frm.AddMessage2
    
          If MULTI_THREAD Then
            task1 = Task(Of UShort).Factory.StartNew(Function() test1.Run)
            task2 = Task(Of UShort).Factory.StartNew(Function() test2.Run)
    
            Task.WaitAll(task1, task2)
    
            result1 = task1.Result
            result2 = task2.Result
          Else
            result1 = test1.Run
            result2 = test2.Run
          End If
    
          RemoveHandler test1.ShowProgress, AddressOf frm.AddMessage1
          RemoveHandler test2.ShowProgress, AddressOf frm.AddMessage2
    
          frm.Close()
        End Using
    
        MessageBox.Show("Result 1: " & result1 & "; Result 2: " & result2)
      End Sub
    
    End Class
    Wenn ich im letzten Code-Abschnitt die Konstante "MULTI_THREAD" auf "False" stelle, funktioniert alles, aber eben sequentiell. Stelle ich sie hingegen auf "True", wird zwar das Formular angezeigt aber es dreht sich eine Sanduhr und es passiert sonst nichts, keine Fortschrittsmeldungen und kein Ergebnis.

    Kann mir jemand sagen, was ich hier falsch mache? Ich habe es auch schon mit normalen Threads (statt der in Framework 4.0 neuen Tasks) bzw. mit Delegates und den dortigen "BeginInvoke" und "EndInvoke" probiert, das Ergebnis blieb aber im Großen und Ganzen dasselbe (keine Fortschrittsanzeige).

    Vielen Dank im Voraus für die Hilfe, mittlerweile weiß ich echt nicht mehr, was ich noch ausprobieren soll.

  • #2
    Ist das Deine Main Methode dort oder passiert da sonst noch was? Ich dachte dass man bei Windows Forms noch etwas bootstrap code braucht. Wenn man eine frische Anwendung erzeugt dann steht der eigentlich auch mit drin. Vielleicht fehlt der ja.

    So sieht eine Windows Forms Anwendung aus wenn man die in C# startet:

    [highlight=c#]
    static class Program
    {
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(fals e);
    Application.Run(new MyMainForm());
    }
    }
    [/highlight]
    Zuletzt editiert von fanderlf; 01.04.2013, 11:36.

    Comment


    • #3
      Das passiert in VB.NEt wohl alles hinter den Kulissen. Ich sage im Projekt, dass es mit der "Sub Main" starten soll, mehr muss ich nicht machen.

      Comment


      • #4
        Die UI kann nur aus dem Thread geändert werden dem die UI gehört, hier dein Hauptthread. Darum führst du den eigentlichen UI-verändernden Code auch im Hauptthread aus. Via dem Invoke Aufruf und nicht direkt im Task Thread. Den Hauptthread und damit die Messageschleife hast du aber per Task.WaitAll angehalten womit der den Code der per Invoke ausgeführt werden soll nicht ausführen kann da seine Messageschleife nie läuft um die Abarbeitung entsprechender Messages durchzuführen. Deine Threads werden also im Me.Invoke Call hängen und darauf warten das der Hauptthread den Code ausführt. Egal was du tust halte niemals den Hauptthread mit der Messageschleife an das ist unfug.
        Zuletzt editiert von Ralf Jansen; 01.04.2013, 12:33. Reason: Rechtschreibung verbessert

        Comment


        • #5
          Oh, danke. Aber ich dachte, Task.WaitAll wartet nur auf das Beenden der übergebenen Tasks, also in meinem Fall der beiden erstellten Tasks, und beeinflusst keine anderen Threads (also auch nicht den Haupt-Thread)?
          Wie bekomme ich es denn dann hin, dass der UI-Thread meine Fortscherittsmeldungen anzeigt und ich trotzdem auf das Beenden beider Tasks warten kann, bevor ich mit dem Ergebnis der Tasks weitermache?

          Comment


          • #6
            Oh, danke. Aber ich dachte, Task.WaitAll wartet nur auf das Beenden der übergebenen Tasks, also in meinem Fall der beiden erstellten Tasks, und beeinflusst keine anderen Threads (also auch nicht den Haupt-Thread)?
            Was denkst du was warten heißt? Es ist natürlich ein Full Stop an der Stelle des Aufrufs und du bist an der Stelle nun mal im Hauptthread.

            Wie bekomme ich es denn dann hin, dass der UI-Thread meine Fortscherittsmeldungen anzeigt und ich trotzdem auf das Beenden beider Tasks warten kann, bevor ich mit dem Ergebnis der Tasks weitermache?
            Ein reines updaten der UI in einem Thread ist sinnfrei ist. Insofern tu ich mich da schwer was sinnvolles zu raten. Gib ein besseres Beispiel und ich hätte vermutlich eine Idee.

            Comment


            • #7
              Also, ich forsche beim Multi-Threading, weil ich ein Tool schreiben soll, welches die Struktur zweier SQL-Datenbanken vergleichen soll. Und da das Einlesen der Struktur einer Datenbank per SMO so unsagbar langsam ist, kam ich auf die Idee, zumindest das Einlesen der Struktur beider Datenbanken zu parallelisieren. Das heißt, ich möchte ein Formular, in dem die beiden Einlese-Threads von Zeit zu Zeit anzeigen, wo sie grad sind, daher die beiden Tasks in meinem Beispiel, die im UI-Thread ihren Fortschritt kundtun sollen.
              Und da ich die zwei DB-Strukturen erst vergleichen kann, wenn sie fertig eingelesen sind, muss ich auch warten, bis beide Tasks abgeschlossen sind, bevor ich weiter machen kann.

              Comment


              • #8
                Dann würde ich einen Backgroundworker nehmen. Der hat vorbereitete Event für den Progress und Completed. Der ist nicht so hip wie Task oder ein Parallel.For aber macht bereits genau das was du brauchst was bei den anderen Geschichten erst nachgebastelt werden müsste.

                Comment


                • #9
                  Ein BackgroundWorker kann aber meines Wissens nur den Fortschritt in ganzzahligen Prozentzahlen angeben, damit kann ich aber nicht dienen, ich möchte daher Textmeldungen als Fortschritt ausgeben.

                  Aber ganz ehrlich, wenn Task.WaitAll alle Threads inklusive des Haupt-Threads blockiert und nicht nur die jeweils übergebenen Threads, dann macht diese Funktionalität überhaupt keinen Sinn...

                  Comment


                  • #10
                    Ein BackgroundWorker kann aber meines Wissens nur den Fortschritt in ganzzahligen Prozentzahlen angeben, damit kann ich aber nicht dienen, ich möchte daher Textmeldungen als Fortschritt ausgeben.
                    Niemand zwingt dich dazu im Progress Event nur die Informationen zu verwenden die du in den EventArgs bekommst. Du kannst aber auch einen eigene Thread Klasse schreiben mit passenderen Events.

                    Aber ganz ehrlich, wenn Task.WaitAll alle Threads inklusive des Haupt-Threads blockiert und nicht nur die jeweils übergebenen Threads, dann macht diese Funktionalität überhaupt keinen Sinn...
                    Es hält nicht alle an es hält an der Stelle im Code an in dem du Task.WaitAll aufrufst. Es hält also den aktuellen Thread an der diese Codezeile aufruft. Alle anderen laufen natürlich weiter wenn sie nicht gerade versuchen sich in den Hauptthread zurück zu synchronisieren. Und Threads zu synchronisieren macht im allgemeinen Sinn auch mit Wait Befehlen. Es macht aber keinen Sinn bei einem UI Thread weil der nun mal die Windowsmessageschleife rotieren lässt (Paint, Key, Mouse Messages etc. die du alle still gelegt hast) Was sollte Task.WaitAll den machen wenn nicht der Stelle des Codes halten bis die Threads auf die man wartet fertig sind?

                    Ein Thread mit einer Windows Messageschleife wie einen UI Thread solltest du nur benachrichtigen mit Events wie Progress, Completed etc. aber eben nicht anhalten weil dein Programm steht wenn dein Hauptthread steht. Das wäre also auch doof wenn dein Code funktioniert hätte. Deine ganzen Application.DoEvents würden nämlich nichts tun da du die Handbremse für den Thread der die Events abarbeiten soll gezogen hast. Die würden erst nach dem WaitAll wieder abgearbeitet werden. Vorher würdest du nichts sehen und nichts bedienen können.

                    Comment


                    • #11
                      Soll ich jetzt also einen Task aufmachen, der dann die beiden Tasks von mir aufmacht, nur damit ich meine Fortschrittsanzeige hinbekomme?

                      Comment


                      • #12
                        Du könntest deine Textmessages in einem Array oder ener Liste ablegen. ReportProgress übermittelt dann den Index des gewünschten Textes und ProgressChanged zeigt den Text anhand des Indexes an
                        Christian

                        Comment


                        • #13
                          Das verstehe ich jetzt nicht? Wo ist denn der Unterschied, ob mein Event den Text oder den Index zu einem Text übermittelt, wenn, wie ich der bisherigen Diskussion entnehmen konnte, der Empfänger grad Pause macht?

                          Comment


                          • #14
                            Wenn du einen Backgroundworker benutzt, wird der UI-Thread nicht angehalten und arbeitet weiter. Er verabeitet die über ProgressChanged kommenden Nachrichten und ändert die UI
                            Christian

                            Comment

                            Working...
                            X