2012-04-08 9 views
2

Ich habe diese Form, die einen neuen Thread hervorbringt und beginnt zu hören und warten auf UDP-Pakete in einer Schleife. Was ich brauche, ist, die Benutzeroberfläche mit der Anzahl der empfangenen Bytes aktualisiert zu halten.Wie wird die Benutzeroberfläche beim Übertragen von Paketen in C# ordnungsgemäß aktualisiert?

Dafür habe ich ein Ereignis eingerichtet, das ich erhalte, sobald ein Paket empfangen wird und übergeben die Anzahl der Bytes als ein Argument erhalten. Da ich den UI-Thread nicht ausführe, kann ich die Benutzeroberfläche nicht einfach direkt aktualisieren. Hier ist, was ich momentan mache:

private void EVENTHANDLER_UpdateTransferProgress(long receivedBytes) { 
    if(InvokeRequired) { 
     Invoke(new MethodInvoker(() => { 
      totalReceivedBytes += receivedBytes; 
      Label.Text = totalReceivedBytes.ToString("##,0"); 
     })); 
    } 
} 

Aber das ist noch auf dem gleichen Thread wie die Paketempfangsschleife läuft und es wird nicht zu dieser Schleife zurückkehren - und für ein anderes Paket warten - bis zu dieser EVENTHANDLER_UpdateTransferProgress Methode kehrt zurück.

Meine Frage ist im Grunde über die folgende Zeile in dem Verfahren, das oben:

Label.Text = totalReceivedBytes.ToString("##,0"); 

Aktualisierung der Benutzeroberfläche wie dies der Paketempfang verlangsamt. Wenn ich diese Leitung abnehme (oder sie kommentiere), wird der Paketempfang viel schneller sein.

Wie kann ich dieses Problem möglicherweise lösen? Ich denke, mehr Threads ist der Schlüssel, aber ich bin mir nicht sicher, wie man sie in dieser Situation richtig implementiert ... Ich verwende Windows Forms mit .NET 2.0.

EDIT:

Auf meinem vorherigen Test scheinen die oben wahr zu sein, und es kann zu einem gewissen Grad tatsächlich. Aber nach ein bisschen mehr Tests realisierte ich, dass das Problem insgesamt Invoke(new MethodInvoker(() => { ... })); Sache war. Wenn ich das entferne (die Benutzeroberfläche wird natürlich nicht aktualisiert werden) und EVENTHANDLER_UpdateTransferProgress lassen, aber das Ereignis weiter erhöhen, ist der Paketempfang viel schneller.

Ich testete den Empfang einiger Dateien, die im Durchschnitt etwa ~ 1,5 Sekunden ohne Aufruf von Invoke() auf dem Event-Handler nahmen. Wenn ich im Event-Handler Invoke() aufgerufen habe, sogar ohne ein Steuerelement in der Benutzeroberfläche zu aktualisieren oder eine Operation auszuführen (mit anderen Worten, der anonyme Methodenkörper war leer), dauerte es viel länger, etwa ~ 5,5 Sekunden. Sie können sehen, es ist ein großer Unterschied.

Gibt es trotzdem etwas zu verbessern?

+0

Welche Version von .NET verwenden Sie? –

+0

@SeanThoman Für diese spezifische Anwendung .NET 2.0. –

+1

Siehe diesen Thread - http://stackoverflow.com/questions/661561/how-to-update-gui-from-another-thread-in-c –

Antwort

3

Das Problem mit Ihrem Ansatz ist, dass es die Benutzeroberfläche für jedes einzelne Paket aktualisiert. Wenn Sie pro Sekunde 1000 Pakete erhalten, würden Sie die Benutzeroberfläche 1000 Mal pro Sekunde aktualisieren! Der Monitor wird wahrscheinlich nicht mehr als 100 Mal pro Sekunde aktualisiert, und niemand wird in der Lage sein, es zu lesen, wenn es mehr als 10 Mal pro Sekunde aktualisiert.

Ein besserer Weg, um dieses Problem anzugehen, ist die totalReceivedBytes += receivedBytes; in den Thread setzen, der die I/O behandelt und einen Timer auf den UI-Thread setzen, der nur einige Male pro Sekunde Label.Text = totalReceivedBytes.ToString("##,0"); ausführt. Wenn die Übertragung beginnt, starten Sie den Timer; Wenn die Übertragung stoppt, stoppen Sie den Timer.

+0

Ja, ich habe schließlich die gleiche Schlussfolgerung nach dem Posten dieser Frage erreicht. Es wird wahrscheinlich viel beschleunigen ... Nur um zu verdeutlichen, sprechen Sie über den WinForms-Timer? Oder gibt es eine bessere Alternative? Ich lese weiter, dass der WinForms-Timer alle möglichen Probleme hat ... Aber mir ist keine Alternative mit einem "Ticker" -Ereignis bekannt. –

+0

Ja, es ist nur der Standard 'System.Windows.Forms.Timer', den Sie auf ein Formular setzen. Ich bin mir seiner Probleme nicht sicher, aber es ist genau das, was Sie für diese Aufgabe wollen. – Gabe

+1

+1 Dies ist der richtige Ansatz. Invoke blockiert den aufrufenden Thread. BeginInvoke funktioniert nicht, aber bei einer hohen Signalisierungsrate kann die GUI-Thread-Nachrichtenwarteschlange schneller mit gunge gefüllt werden, als sie geleert werden kann. Eine 1 Sekunde Forms.Timer ist ein besserer Ansatz. Dies ist einer der wenigen Male, wenn Umfrage die bessere Option ist :) –

1

Ja, es gibt eine Möglichkeit, dies zu verbessern.

Die erste besteht darin, BeginInvoke anstelle von Invoke zu verwenden, die nicht auf den Aufruf warten wird, um zurückzukehren. Sie sollten auch

private void EVENTHANDLER_UpdateTransferProgress(long receivedBytes) { 
    if(InvokeRequired) { 
     BeginInvoke(new Action<long>(EVENTHANDLER_UpdateTransferProgress), 
        receivedBytes)); 
     return; 
    } 
    totalReceivedBytes += receivedBytes; 
    Label.Text = totalReceivedBytes.ToString("##,0"); 
} 

Also mit einer anderen Form in Ihrer Methode betrachten, wenn Sie diese Methode von einer Methode aufrufen, die nicht auf der GUI, das Update erfordert Berufung auf noch ausgeführt.


Eine andere Option, die Sie tun können, ist das Abbrechen eines Threads in Ihrem Download-Thread. Etwas in der Likes von

public event EventHandler<MonitorEventArgs> ReportProgress; 

public void startSendingUpdates(MonitorEventArgs args) { 
    EventHandler<MonitorEventArgs> handler = ReportProgress; 
    if (handler == null) { 
     return; 
    } 
    ThreadPool.QueueUserWorkItem(delegate { 
     while (!args.Complete) { 
      handler(this, args); 
      Thread.Sleep(800); 
     } 
    }); 
} 

public void download() { 
    MonitorEventArgs args = new MonitorEventArgs(); 
    startSendingUpdates(args); 
    while (downloading) { 
     int read = downloadData(bytes); 
     args.BytesTransferred += read; 
    } 
    args.Complete = true; 
} 

public class MonitorEventArgs : EventArgs { 
    public bool Complete { get; set; } 
    public long BytesTransferred { get; set; } 
} 

Der Overhead von diesem ist etwas klein im Vergleich zu den Vorteilen. Ihr Download-Thread ist von den Aktualisierungen der GUI nicht betroffen (zumindest nicht im Vergleich zur Aktualisierung auf der GUI).Der Nachteil ist, dass du einen Thread im Threadpool besetzst, aber hey, dafür sind sie da! Und der Thread wird beendet, wenn es fertig ist, da Sie die komplette Flagge gesetzt haben. Sie müssen bei der Einstellung auch nicht sperren, da ein zusätzlicher Lauf im Worker-Thread im Kontext unwichtig ist.

+0

Will 'BeginInvoke' anstelle von' Invoke' versuchen, kann es ein wenig helfen. Aber ich glaube, dass das eigentliche Problem mit @ Gabes Antwort oben behoben wird. Der Event-Handler wird niemals von einer Methode aufgerufen, die nicht aufgerufen werden muss. Nicht zumindest in diesem Programm. Aber danke für den Tipp, es könnte sich in der Zukunft als nützlich erweisen. –

+0

@RicardoAmaral: Das Konzept ist das gleiche. In meiner Version sendet der Sende-Thread Updates alle 800 Millisekunden, in der Version von Gabe hat die GUI einen Timer, der eine bestimmte Anzahl von Malen pro Sekunde aktualisiert. Also tun sie das gleiche ... – Patrick

+0

@RicardoAmaral: Ich bin mir nicht sicher, ob Sie es bemerkt haben, aber 'ThreadPool.QueueUserWorkItem' startet tatsächlich einen anderen Thread, so dass Ihre Download-Methode weiterhin eigenständig läuft ... Also im Grunde ist es ein Timer, mit einer Wiederholung von 800 Millisekunden, die endet, wenn die "args.Complete" ist "wahr" – Patrick

0

Haben Sie versucht, BeginInvoke statt Invoke zu verwenden? BeginInvoke() ist ein asynchroner Aufruf.

+0

Wie @Patrick bereits vorgeschlagen (er war a paar Minuten schneller lol), ich werde das versuchen und sehen, ob es hilft, aber wie ich sagte, ich habe in seiner Antwort kommentiert, ich glaube, die richtige Antwort ist Gabe's Antwort. Müssen jedoch einige Tests durchführen. –

Verwandte Themen