Winforms-Updates mit hoher Leistung

Lassen Sie mich diese Frage mit einigen Hintergrundinformationen einrichten. Wir haben einen langwierigen Prozess, der Daten in einem Windows Form generiert. Offensichtlich wird eine Form von Multithreading benötigt, um das Formular reaktionsfähig zu halten. Es ist jedoch auch erforderlich, dass das Formular so oft wie möglich pro Sekunde aktualisiert wird, ohne jedoch die Reaktionszeiten zu beeinträchtigen.

Hier ist ein einfaches Testbeispiel mit einem Hintergrund-Worker-Thread:

void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        int reportValue = (int)e.UserState;
        label1.Text = reportValue;
        //We can put this.Refresh() here to force repaint which gives us high repaints but we lose
        //all other responsiveness with the control

    }

    void bw_DoWork(object sender, DoWorkEventArgs e)
    {
            for (int x = 0; x < 100000; x++)
            {     
              //We could put Thread.Sleep here but we won't get highest performance updates
                bw.ReportProgress(0, x);                    
            }
    }

Bitte beachten Sie die Kommentare im Code. Bitte hinterfragen Sie auch nicht, warum ich das möchte. Die Frage ist einfach: Wie erzielen wir die höchste Wiedergabetreue bei der Aktualisierung des Formulars bei gleichzeitiger Beibehaltung der Reaktionsfähigkeit? Wenn Sie das Repaint erzwingen, erhalten Sie zwar Updates, wir verarbeiten jedoch keine Windows-Nachrichten.

Ich habe auch versucht, DoEvents zu platzieren, aber das erzeugt Stapelüberlauf. Was ich brauche, ist eine Art zu sagen: "Verarbeiten Sie alle Windows-Nachrichten, wenn Sie in letzter Zeit nicht haben". Ich kann auch erkennen, dass möglicherweise ein etwas anderes Muster erforderlich ist, um dies zu erreichen.

Anscheinend müssen wir ein paar Probleme lösen:

Aktualisieren des Formulars über den Nicht-UI-Thread. Für dieses Problem gibt es eine ganze Reihe von Lösungen, z. B. Aufruf, Synchronisationskontext und Hintergrundmuster für Worker.Das zweite Problem besteht darin, das Formular mit zu vielen Aktualisierungen zu überfluten, wodurch die Nachrichtenverarbeitung blockiert wird. Dies ist das Problem, um das es in meiner Frage wirklich geht. In den meisten Beispielen wird dies trivial gehandhabt, indem die Anforderungen mit einer willkürlichen Wartezeit verlangsamt werden oder nur alle X% aktualisiert werden. Weder sind diese Lösungen für reale Anwendungen geeignet, noch erfüllen sie die maximalen Aktualisierungsanforderungen, während sie auf Kriterien reagieren.

Einige meiner ersten Ideen, wie ich damit umgehen soll:

Stellen Sie die Elemente im Hintergrund-Worker in die Warteschlange und verteilen Sie sie dann in einem UI-Thread. Dies stellt sicher, dass jeder Gegenstand bemalt ist, führt aber zu Verzögerungen, die wir nicht wollen.Verwenden Sie möglicherweise TPLVerwenden Sie möglicherweise einen Zeitgeber in dem UI-Thread, um einen Aktualisierungswert anzugeben. Auf diese Weise können wir die Daten mit der schnellsten Rate erfassen, die wir verarbeiten können. Der Zugriff auf / die gemeinsame Nutzung von Daten über mehrere Threads hinweg ist erforderlich.

Update, ich habe aktualisiert, um einen Timer zum Lesen einer gemeinsam genutzten Variablen mit den Updates des Hintergrund-Worker-Threads zu verwenden. Aus irgendeinem Grund führt diese Methode nun zu einer guten Formularantwort und ermöglicht es dem Hintergrund-Worker, etwa 1.000-mal so schnell zu aktualisieren. Aber interessanterweise ist es nur 1 Millisekunde genau.

Wir sollten also in der Lage sein, das Muster zu ändern, um die aktuelle Zeit zu lesen und die Aktualisierungen vom bw-Thread abzurufen, ohne den Timer zu benötigen.

Hier ist das neue Muster:

//Timer setup
{
            RefreshTimer.SynchronizingObject = this;
            RefreshTimer.Elapsed += RefreshTimer_Elapsed;
            RefreshTimer.AutoReset = true;
            RefreshTimer.Start();
}           

     void bw_DoWork(object sender, DoWorkEventArgs e)
            {
                    for (int x = 0; x < 1000000000; x++)
                    {                    
                       //bw.ReportProgress(0, x);                    
                       //mUiContext.Post(UpdateLabel, x);
                        SharedX = x;
                    }
            }

        void RefreshTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            label1.Text = SharedX.ToString();
        }

Aktualisieren Und hier haben wir die neue Lösung, die den Timer nicht benötigt und den Thread nicht blockiert! Mit diesem Muster erzielen wir eine hohe Rechenleistung und Genauigkeit bei den Aktualisierungen. Leider ist TickCount nur 1 MS genau. Wir können jedoch eine Reihe von X-Aktualisierungen pro MS durchführen, um schneller als 1 MS zu sein.

   void bw_DoWork(object sender, DoWorkEventArgs e)
    {
            long lastTickCount = Environment.TickCount;                
            for (int x = 0; x < 1000000000; x++)
            {
                if (Environment.TickCount - lastTickCount > 1)
                {
                    bw.ReportProgress(0, x);
                    lastTickCount = Environment.TickCount;
                }                 
            }
    }

Antworten auf die Frage(2)

Ihre Antwort auf die Frage