Announcement

Collapse
No announcement yet.

ProgressBar/DoEvents() vs backgroundWorker

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

  • ProgressBar/DoEvents() vs backgroundWorker

    Hi

    Ich habe ein Fenster von einem Vorgänger übernommen, das eigentlich nur aus einem Label, einer ProgressBar und einem Button (zum Abbrechen) besteht. Dieses Fenter wird üblicherweise vor einer Schleife erzeugt (oder wenn es schon existiert nur der Label geändert) und innerhalb der Schleife, die durch das Fenster dem Benutzer verdeutlicht werden soll, wird jedesmal showinfo("",wert) aufgerufen, um die Ansicht zu aktualisieren.

    Nun habe ich, um bei größeren Datenbankoperationen ebenso dieses Fenster nutzen zu können, die Option Wert=-1 hinzugefügt, um den Style auf Marquee zu setzen.

    [highlight=vb.net]

    Class frmProgress

    ...
    Public Sub showinfo(ByVal s As String, ByVal proz As Double)

    Dim neu As Boolean : neu = False

    If s <> "" And s <> lblFortschritt.Text Then 'Bei Angabe des Textes in einer Schleife ist nicht jedesmal ein DoEvents nötig
    lblFortschritt.Text = s
    neu = True
    End If
    If proz = -1.0 Then
    prgFortschritt.Style = ProgressBarStyle.Marquee
    Else
    Dim p As Integer : p = Fix(proz)
    If p > 100 Then p = 100
    If p <> prgFortschritt.Value Then
    prgFortschritt.Value = p
    neu = True
    End If
    If neu Then Application.DoEvents()
    End If

    End Sub
    End Class

    Sub Beispiel()
    Dim Anzahl As Integer = irgendwasgroßes
    Dim i As Integer
    Dim f As New frmProgress()
    f.showinfo("Schleife1", 0.0)
    f.Show(Me)
    For i = 1 To Anzahl
    f.showinfo("", i * 100 / Anzahl)
    If f.Abbruch Then Exit For
    'Do Something
    Next

    f.showinfo("Operation2", -1.0)
    'externer Funktionsaufruf/Datenbankoperation
    f.Close()
    Application.DoEvents
    [/highlight]

    Das Problem ist nun, dass ich einerseits eigentlich die DoEvents() vermeiden wollte, es aber andererseits notwendig ist, damit überhaupt gezeichnet wird (beim Marquee zeichnet er auch nur den Start, aber bewegen tut sich nichts), und auch, damit der Klick auf den Cancel-Button registriert wird. Es ist schon so eigentlich inakzeptabel, dass der Klick auf den Cancel-Button erst dann registriert wird, wenn der Fortschritt groß genug ist, wieder einmal den Balken zu verlängern, auf der anderen Seite, möchte ich aber auch nicht in jedem Schleifendurchlauf alle Events dieses Programms vollständig abarbeiten, sonst brennt mir irgendwann der Prozessor durch.

    Gibt es eine Möglichkeit, auch ohne DoEvents die Applikation dazu zu bringen trotzdem zu zeichnen und auf den Abbrechen Button zu reagieren?

    PerformStep gestaltet sich extrem umständlich, weil die frmProgress ja nicht weiß, wie groß der jeweilige Step ist. Bei einer Schleife mit nur 3 Durchläufen wäre der Step 33, bei einer Schleife über 2000 Datensätze müsste ich 20 mal abwarten, bevor ich den Step 1 ausführe, und vorher den Value um den Step verringert zu setzen, um bei PerformStep den korrekten Step auszuführen, führt dazu, dass ich das Unterschreiten des Value 0 abfangen muss. Gut, ich könnte mir den Step ausrechnen aus dem aktuellen Value der ProgressBar und dem zu dem er gesetzt werden soll, das hilft mir aber nicht beim Marquee, der scheint sich nur durch das explizite Ausführen der Events zu bewegen.

    Aus anderen Beiträgen erlese ich, dass ein eigener Thread/BackgroundWorker empfohlen wird, da stelle ich es mir aber etwas schwierig vor einerseits die Kommunikation zu regeln (ich müsste da jedesmal per Invoke dem anderen Thread sagen, dass der Balken aktualisiert werden soll und die Abbrechen-Eigenschaft abfragen) und mit BackgroundWorkern kenne ich mich nicht aus, gibt es da einen guten Link, um da was nachzulesen? Außerdem wird das frmProgress an so vielen Stellen im Programm verwendet, dass ich nur sehr ungern die Schnittstelle ändern würde.

    Vielleicht weiß ja jemand Rat
    Gruß
    Martin

  • #2
    Hi,

    ich habe inzwischen mal versucht, mich über MSDN über den Backgroundworker schlau zu machen, aber irgendwie steige ich da wohl nicht so richtig durch.

    Ich wollte ja eigentlich das ganze in einen BGW auslagern, um nicht immer mit Application.DoEvents() die komplette Meldungsqueue abarbeiten zu müssen, nur damit der Fortschritt in dem ProgressBar auch angezeigt wird, bzw. die Problematik zu umgehen, dass ein auf Marquee gesetzter ProgressBar nicht "läuft" ohne dass ich ständig Application.DoEvents() aufrufen muss.

    Wenn ich das aber auf MSDN richtig sehe, steht da im Beispiel Datei herunterladen
    [highlight=vb.net]
    Me.BackgroundWorker1.RunWorkerAsync()
    While Me.backgroundWorker1.IsBusy
    progressbar1.Increment(1)
    Application.DoEvents()
    End While
    [/highlight]

    Erstens habe ich also doch (sogar noch schlimmer als vorher) ständig das Application.DoEvents() im Nacken, auf der anderen Seite ist hier in Sekundenbruchteilen (unabhängig von einer Kopplung mit dem eigentlichen Prozess) sofort der ProgressBar voll.

    Ein (zugegeben eher konfuser) Ansatz für mich sähe nun so aus:
    [highlight=vb.net]
    Private WithEvents bgw as BackgroundWorker
    Private frm as frmFortschritt
    Private CancelledByBgw As Boolean

    Public Sub DoMyWork(sender As Object, e As DoWorkEventArgs)
    Dim Aufwand As Integer = BerechneAufwand
    Dim abgearbeitet as Integer
    CancelledByBgw = False
    Try
    For abgearbeitet = 0 To Aufwand
    DoSomething()
    CType(sender, BackgroundWorker).ReportProgress(abgearbeitet*100/Aufwand)
    Next
    Catch
    CType(sender, BackgroundWorker).CancelAsync()
    CancelledByBgw = True
    End Try
    End Sub

    Public Sub UpdateProgress(sender As Object, e As ProgressChangedEventArgs)
    if not frm Is Nothing then frm.showinfo("", e.ProgressPercentage)
    End Sub

    Public Function WorkForALongTime(...) As Boolean
    bgw = New BackgroundWorker()
    AddHandler bgw.DoWork, AddressOf DoMyWork
    AddHandler bgw.ProgressChanged, AddressOf UpdateProgress

    frm = New frmFortschritt(Abbrechbar:=True)
    bgw.RunWorkerAsync()
    While not frm.Abbruch And bgw.IsBusy
    Application.DoEvents()
    End While
    If frmAbbrechen And Not bgw.CancellationPending then bgw.CancelAsync()
    WorkForALongTime = Not(CancelledByBgw Or frm.Abbruch)
    frm.Close()
    Application.DoEvents()
    frm.Dispose()
    Application.DoEvents()
    End Function
    [/highlight]

    Muss das wirklich so kompliziert sein, oder gibt es da nichts einfacheres, wenn man auch eine Antwort vom BGW haben möchte? Hinzu kommt, in welchem Thread wird das ProgressChanged-Event bearbeitet? Im BGW-Thread wäre frm nämlich nicht zugreifbar, und kann ich überhaupt aus dem BGW heraus CancelledByBgw verändern, oder bekomme ich da auch den klassischen "Die Ressource ist einem anderen Thread zugeordnet."?

    Gibt es irgendwo ein relativ ausführliches Tutorial zum BGW, in dem man mal etwas tiefer nachlesen kann, was da abläuft, weil ehrlich gesagt sind mir die Beispiele, die MSDN liefert etwas oberflächlich...

    Gruß
    Martin Dietz

    Comment


    • #3
      Hallo Martin,

      vielleicht kommst du mit dem Beispiel unter BackgroundWorker-Klasse (d.h. unter "Informationen über..."), nämlich der Fibonacci-Berechnung, besser klar. Ich hatte damit keine Probleme, den Arbeitsfortschritt anzuzeigen.

      Gruß Jürgen

      Comment


      • #4
        Originally posted by M.Dietz View Post
        [highlight=vb.net]
        While not frm.Abbruch And bgw.IsBusy
        Application.DoEvents()
        End While
        [/highlight]
        Hi M.Dietz,

        schau doch mal ob mit dieser kleinen Änderung Dein Programm besser läuft. Durch den Thread.Sleep(1000) wird nur noch jede Sekunde der DoEvents ausgelöst.

        [highlight=vb.net]
        While not frm.Abbruch And bgw.IsBusy
        Thread.Sleep(1000)
        Application.DoEvents()
        End While
        [/highlight]

        Gruß Womble

        Comment

        Working...
        X