4 Stimmen

C# Parallel.For und UI Aktualisierung?

Ich versuche, eine Parallel.ForEach-Schleife zu implementieren, um eine alte foreach-Schleife zu ersetzen, aber ich habe Schwierigkeiten, meine UI zu aktualisieren (ich habe einen Zähler, der etwas wie 'x/y Dateien verarbeitet' anzeigt). Ich habe ein Beispiel für eine Parallel Forloop erstellt, um mein Problem zu veranschaulichen (das Label wird nicht aktualisiert).

Verwenden von System;
Verwenden von System.Windows.Forms;
Verwenden von System.Threading.Tasks;
Verwenden von System.Threading;

Namespace FormThreadTest
{
    Öffentliche teilklasse Form1 : Form
    {
        Private Synchronisierungskontext m_sync;
        Privater System.Timers.Timer m_timer;
        Privates int m_count;

        Öffentliche Form1()
        {                       
            InitializeComponent();

            m_sync = Synchronisierungskontext.Current;

            m_count = 0;

            m_timer = Neuer System.Timers.Timer();
            m_timer.Interval = 1000;
            m_timer.AutoReset = true;
            m_timer.Elapsed += new System.Timers.ElapsedEventHandler(m_timer_Elapsed);
            m_timer.Start();
        }

        Private void m_timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            Task.Factory.StartNew(() =>
            {
                m_sync.Post((o) =>
                {
                    label1.Text = m_count.ToString();
                    Application.DoEvents();
                }, null);
            });
        }

        Private void button1_Click(object sender, EventArgs e)        
        {     
            Task.Factory.StartNew(() =>
            {
                Parallel.For(0, 25000000, delegate(int i)
                {
                    m_count = i;
                });
            });
        }
    }
}

Wenn ich die Methode für das Klicken auf den Schaltflächen-Event ändere und ein Thread.Sleep() hinzufüge, scheint dies dem UI-Aktualisierungsthread Zeit zu geben, um seine Arbeit zu erledigen:

private void button1_Click(object sender, EventArgs e)        
        {     
            Task.Factory.StartNew(() =>
            {
                Parallel.For(0, 25000000, delegate(int i)
                {
                    m_count = i;
                    Thread.Sleep(10);
                });
            });
        }

Gibt es keine Möglichkeit, das Sleep zu vermeiden, oder muss ich es dort haben? Es scheint, dass meine UI das Label nicht aktualisieren wird, es sei denn, ich tue es? was ich seltsam finde, da ich das Anwendungsfenster verschieben kann (es blockiert sich nicht) - warum aktualisiert sich das Label also nicht, und wie kann ich meinen Code ändern, um Parallel.For(Each) und UI-Updates besser zu unterstützen?

I've been searching for a solution, but I can't seem to find anything (or I maybe searching for the wrong thing?).

Mit freundlichen Grüßen Simon

4voto

BonanzaDriver Punkte 6152

Ich habe eine ähnliche Anforderung, um meine GUI zu aktualisieren, wenn die Ergebnisse in einem Parallel.ForEach() eintreffen. Ich bin jedoch einen ganz anderen Weg gegangen als du.

public partial class ClassThatUsesParallelProcessing
{
    public event ProcessingStatusEvent StatusEvent;

    public ClassThatUsesParallelProcessing()
    { }

    private void doSomethingInParallel()
    {
        try
        {
            int counter = 0;
            int total = listOfItems.Count;

            Parallel.ForEach(listOfItems, (instanceFromList, state) =>
            {
                // führen Sie hier Ihre Arbeit aus ...

                // Bestimmen Sie Ihren Fortschritt und lösen Sie ein Ereignis aus, zurück an alle, die interessiert sind ...
                int count = Interlocked.Increment( ref counter );

                int percentageComplete = (int)((float)count / (float)total * 100);
                OnStatusEvent(new StatusEventArgs(State.UPDATE_PROGRESS, percentageComplete));
            }
        }
        catch (Exception ex)
        {

        }
    }
}

Ihre GUI würde dann etwas Ähnliches wie folgt aussehen:

private void ProcessingStatusEventHandler(object sender, StatusEventArgs e)
{
    try
    {
        if (e.State.Value == State.UPDATE_PROGRESS)
        {
            this.BeginInvoke((ProcessHelperDelegate)delegate
            {
                this.progressBar.Value = e.PercentageComplete;
            }
        }
    }
    catch { }
}

Der einzige Punkt, den ich hier machen möchte, ist, dass Sie bestimmen können, wann es sinnvoll ist, den Fortschritt durch Ihre Schleife zu bestimmen. Und da diese Schleifeniterationen auf Hintergrundthreads stattfinden, müssen Sie die Logik zur Aktualisierung der GUI-Steuerung auf Ihren Haupt-(Disponierungs-)Thread zurückführen. Dies ist nur ein einfaches Beispiel - stellen Sie einfach sicher, dass Sie das Konzept befolgen und alles wird gut.

2voto

Timbo Punkte 26346

Meine Vermutung ist, dass das Zählen bis 25 Millionen (parallel!) weniger als eine Sekunde dauert... daher wird Ihr Timer nicht feuern, bevor das Zählen abgeschlossen ist. Wenn Sie das Thread.Sleep hinzufügen, wird das Ganze viel langsamer laufen, so dass Sie die Updates sehen können.

Andererseits sieht Ihr Timer-Event-Handler unordentlich aus. Sie starten einen Thread, um eine Nachricht an Ihre Benutzeroberfläche zu senden, und wenn Sie schließlich auf Ihrem UI-Thread sind, rufen Sie Application.DoEvents auf... warum? Sie sollten in der Lage sein, sowohl die Task-Erstellung als auch den DoEvents-Aufruf zu entfernen.

Bearbeiten: Ich habe das von Ihnen gepostete Programm getestet und sah die Beschriftung zweimal aktualisieren. Mein Computer braucht mehr als eine Sekunde, um bis 25m zu zählen. Ich habe die Zahl auf 1 Milliarde erhöht, und die Beschriftung aktualisiert sich mehrmals.

Bearbeiten2: Sie können den Timer-Handler auf folgendes reduzieren

    private void m_timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        m_sync.Post((o) =>
        {
            label1.Text = m_count.ToString();
        }, null);
    }

Leider entspricht die angezeigte Zahl nicht der Anzahl der derzeit verarbeiteten Elemente, sondern dem Index des Elements, das zufällig in dem Moment verarbeitet wurde, als das Timer-Ereignis ausgelöst wurde. Sie müssen das Zählen selbst implementieren. Dies kann durch Verwenden von

Interlocked.Add(ref m_count, 1);

1voto

Bonshington Punkte 3752

Für Parallelversuch

//private void m_timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)

System.Threading.Tasks.Task.Factory.StartNew(() =>
{
  m_sync.Post((o) =>
  {
    label1.Text = m_count.ToString();
    Application.DoEvents();
  }, null);

  System.Threading.Thread.Sleep(1000;)

}, System.Threading.Tasks.TaskCreationOptions.LongRunning);

aber es könnte nicht funktionieren. Sie könnten eine Thread-Ausnahme erhalten, da die WinForms-Steuerung einem anderen Thread, der nicht der Ersteller-Thread ist, nicht erlaubt, sie zu aktualisieren. Wenn das der Fall ist, versuchen Sie, eine andere Methode zu erstellen und die Steuerung selbst als Delegat zu verwenden.

z.B. label1.Invoke(updateUIHandler);

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X