Announcement

Collapse
No announcement yet.

BackgroundWorker ReportProgress (Update Textbox zu langsam / gar nicht)

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

  • BackgroundWorker ReportProgress (Update Textbox zu langsam / gar nicht)

    Hallo Leute,

    ich habe einen Backgroundworker implementiert um wirklich sehr rechenintensive Routinen auszulagern.
    Die Routinen geben jeweils ein String zurück.

    Diesen übergebe ich an ReportProgess mittels:

    Code:
    myBgWorker.ReportProgress(0, currentString);
    Mein ProgressChange Event schaut so aus:
    Code:
         void myBgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
            {
                txtInfo.AppendText(e.UserState.ToString() + "\n");
                txtInfo.ScrollToEnd();
            }
    Also dieses Event wird mehrere 1000 male aufgerufen um den aktuellen Fortschritt zu übergeben (nur den String, welcher einer TextBox angehängt wird)
    Ich habe jedoch das Problem, dass (so macht es mir den anschein) die GUI nicht hinterkommt die Textbox upzudaten. Und das Programm hängt wieder, bis der BGWorker durchgelaufen ist und dann wird auch meine Textbox richtig dargestellt (die Einträge).

    Aus Testzwecken habe ich versucht eine nicht ganz so rechenintensive Routine updaten zulassen (10-100 Events) dann stellt die Textbox den Verlauf richtig und vor allem das wichtigste "LIVE" dar.

    Hat jemand eine Idee um das Problem zu lösen? Sollte ich keine Textbox (multiline) verwenden sondern ein anderes Control?

    Die Anwendung wird in WPF + C# geschrieben.


    Danke euch.

  • #2
    Hallo,

    lange Rede (kommt unten) kurzer Sinn: schick nicht jede Änderung an die UI, sondern batch-weise. Genaueres steht unten.

    Die Anwendung wird in WPF + C# geschrieben.
    Dann würde ich die Textbox nicht direkt bearbeiten sondern eine Listbox nehmen und die Einträge per Datenbindung anzeigen lassen. D.h. die Einträge müssen nur der zugrunde liegenden ObservableCollection<string> hinzugefügt werden. Alternativ kann auch statt dem <string> ein eigener Typ verwendet werden um zB auch Symbole anzeigen zu lassen - zB wenn eine Fehler kommt dann ein X oder bei Erfolg für einen Schritt einen Haken, etc. Also Typ der zB so aussieht:
    [highlight=c#]
    public class Progress
    {
    public State State { get; set; }
    public string Message { get; set; }
    }

    public enum State
    {
    Success,
    Failure
    }
    [/highlight]
    Entsprechend ein DataTemplate erstellen das die Darstellung dieses Typs in der View managed. Für den State -> Bild ist ein State2ImageConverter notwendig -> siehe ValueConverter.

    Zurück zum eigentlichen Problem des hinterhänges der Aktualisierung:
    Ich würde auch auf den Backgroundworker verzichten und stattdessen Tasks verwenden. Damit bist du flexibler. Damit es nicht zu einem Cross-Thread-Problem der UI kommt ist dann halt entweder Dispatcher.Invoke/BeginInvoke notwendig oder (was hier eher nicht zutreffen wird) eine Task-Continuation die den CurrentSynchronisationContext verwendet.

    Es geht also darum dass Aufgaben vom nebenläufigen Thread in den UI-Thread kommen und dafür ist der Backgroundworker eine einfache Möglichkeit. Dieser delegiert die Aufgaben asynchron in die UI per SynchronizationContext.Post (wenn ich micht nicht irre) was grundsätzlich schon besser ist die synchrone Übergabe per Control.Invoke/Dispatcher.Invoke/SynchronizationContext.Send. Da dies aber immer noch zu langsam ist muss eine andere Lösung gesucht werden.

    Hier bietet es sich an nicht jeden Fortschritt an die UI zu melden sondern das batch-weise durchzuführen. D.h. die async. Operation sammelt zB 10 Einträge (vom Typ Progress - siehe oben) und sendet dann alles 10 auf einmal an die UI. Bzw. exakter feuert ein Event und der Eventhandler delegiert das in die UI per Dispatcher.BeginInvoke(Dispatcher.Priority.DataBou nd) und dort wird dann der ObservalbeCollection<Progress> die 10 Einträge (bzw. alle Einträge des Batch) hinzugefügt. Die Listbox aktualisiert sich dann automatisch.

    Der Parameter 10 kann auch angepasst werden um zum Einen eine flüssige UI zu haben und zum Anderen trotzdem noch live den Fortschritt zu sehen.

    Für das oben erwähnte Event sind auch EventArgs notwendig:
    [highlight=c#]
    public class ProgressEventArgs : EventArgs
    {
    public IEnumerable<Progress> ProgressBatch { get; prüvate set; }

    public ProgressEventArgs(IEnumerable<Progress> progressBatch)
    {
    Contract.Requires<ArgumentNullException>(progressB atch != null);
    this.ProgressBatch = progressBatch;
    }
    }
    [/highlight]

    So ich denke das sollten genügend Anregungen sein
    Viel Spass.


    mfG Gü
    "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

    Comment


    • #3
      Hallo gfoidl,

      vielen Dank für Deine ausführlichen Anregungen.
      Habe es zwar beim BackgroundWorker belassen, jedoch es wirklich über die Datenbindung und des Intervals gelöst.
      Funktioniert nun auch wunderbar.

      Ich Danke Dir.

      sql_insider

      Comment

      Working...
      X