Threadsafe GUI Update

  • C#

Es gibt 17 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    Threadsafe GUI Update

    Folgender Code aus einem Codeproject-Artikel schein mir interessant zu sein:

    C#-Quellcode

    1. public static TResult SafeInvoke<t,>(this T isi, Func<t,> call) where T : ISynchronizeInvoke
    2. {
    3. if (isi.InvokeRequired) {
    4. IAsyncResult result = isi.BeginInvoke(call, new object[] { isi });
    5. object endResult = isi.EndInvoke(result); return (TResult)endResult;
    6. }
    7. else
    8. return call(isi);
    9. }
    10. public static void SafeInvoke<t>(this T isi, Action<t> call) where T : ISynchronizeInvoke
    11. {
    12. if (isi.InvokeRequired) isi.BeginInvoke(call, new object[] { isi });
    13. else
    14. call(isi);
    15. }

    Leider versagen meine Kenntnisse in C#, um ein VB-Äquivalent zu entwickeln.
    Für Hilfe wäre ich sehr dankbar !!!
    Ich habs aber auch nur ausgebessert und durch den Converter gejagt..

    VB.NET-Quellcode

    1. <System.Runtime.CompilerServices.Extension> _
    2. Public Shared Function SafeInvoke(Of t)(isi As T, [call] As Func(Of t)) As TResult
    3. If isi.InvokeRequired Then
    4. Dim result As IAsyncResult = isi.BeginInvoke([call], New Object() {isi})
    5. Dim endResult As Object = isi.EndInvoke(result)
    6. Return DirectCast(endResult, TResult)
    7. Else
    8. Return [call](isi)
    9. End If
    10. End Function
    11. <System.Runtime.CompilerServices.Extension> _
    12. Public Shared Sub SafeInvoke(Of t)(isi As T, [call] As Action(Of t))
    13. If isi.InvokeRequired Then
    14. isi.BeginInvoke([call], New Object() {isi})
    15. Else
    16. [call](isi)
    17. End If
    18. End Sub
    Hi
    ich kenne die Schreibweise mit fehlendem zweiten Parameter nicht, würde aber vermuten, dass du zusätzlich zu (Of t) (bzw. Of T) noch TResult angeben musst: (Of T, TResult). Oder besser noch T zu was sinnvollem erweitern, z.B. TParam.
    Was genau das BeginInvoke und EndInvoke gegenüber Invoke bringen soll, ist mir ein wenig ein Rätsel.

    Viele Grüße
    ~blaze~
    Gibt es nicht, EndInvoke wartet genauso wie Invoke, bis es beendet wird. Sinnvoller wäre also wohl nicht [call] direkt aufzurufen, sondern eine zweite Methode zu erstellen, welche dann EndInvoke aufruft und erst dann den call, jedoch bräuchte man dann eine andere Möglichkeit das Ergebnis zurückzugeben, z.B. TResult als Parameter für den Call. Für Invokes ohne Rückgabetyp(void) sowieso Sinnvoll, da ändert sich ja der call nicht mal.
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Da weder der Code von @us4771 noch der von @ThuCommix übersetzte Code richtig sind, hab ichs jetzt hier mal für C# und VB.NET neu geschrieben. So sieht das ganze in VB.NET und C# richtig aus:
    VB.NET

    VB.NET-Quellcode

    1. Public Module SafeInvokeExtensions
    2. <System.Runtime.CompilerServices.Extension> _
    3. Public Function SafeInvoke(Of TTarget As ISynchronizeInvoke, TResult)(target As TTarget, func As Func(Of TResult)) As TResult
    4. If target.InvokeRequired Then
    5. Return DirectCast(target.Invoke(func, Nothing), TResult)
    6. End If
    7. Return func()
    8. End Function
    9. <System.Runtime.CompilerServices.Extension> _
    10. Public Sub SafeInvoke(Of TTarget As ISynchronizeInvoke)(target As TTarget, method As Action)
    11. If target.InvokeRequired Then
    12. target.BeginInvoke(method, Nothing)
    13. Else
    14. method()
    15. End If
    16. End Sub
    17. <System.Runtime.CompilerServices.Extension> _
    18. Public Sub SafeBeginInvoke(Of TTarget As ISynchronizeInvoke)(target As TTarget, method As Action)
    19. target.BeginInvoke(method, Nothing)
    20. End Sub
    21. End Class
    C#

    C#-Quellcode

    1. public static class SafeInvokeExtensions
    2. {
    3. public static TResult SafeInvoke<TTarget, TResult>(this TTarget target, Func<TResult> func)
    4. where TTarget : ISynchronizeInvoke
    5. {
    6. if (target.InvokeRequired)
    7. {
    8. return (TResult)target.Invoke(func, null);
    9. }
    10. return func();
    11. }
    12. public static void SafeInvoke<TTarget>(this TTarget target, Action method)
    13. where TTarget : ISynchronizeInvoke
    14. {
    15. if (target.InvokeRequired)
    16. {
    17. target.BeginInvoke(method, null);
    18. }
    19. else
    20. {
    21. method();
    22. }
    23. }
    24. public static void SafeBeginInvoke<TTarget>(this TTarget target, Action method)
    25. where TTarget : ISynchronizeInvoke
    26. {
    27. target.BeginInvoke(method, null);
    28. }
    29. }

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „nafets“ ()

    Was ich im Momemnt daraus verkürzt habe:

    VB.NET-Quellcode

    1. <System.Runtime.CompilerServices.Extension>
    2. Public Sub SafeInvoke(ctrl As Control, act As Action)
    3. If ctrl.InvokeRequired Then
    4. ctrl.BeginInvoke(act)
    5. Else
    6. act()
    7. End If
    8. End Sub

    Macht dort async/await Sinn, und wenn ja, wie?
    EDIT://
    Habe die Bearbeitung von @nafets zu spät gesehen, danke dafür. Was dort nicht seine darf, ist die Kennzeichnung einer Methode in einem Modul als Shared.
    Sieht aber sonst gut aus. Ich schau's mir an.
    @us4711 Vielleicht solltest Du erwägen, mal nicht auf .InvokeRequired zu testen und sofort zu invoken.
    Da passiert nix ungewöhnliches. Es dauert vielleicht ein paar Takte länger, ist aber als Quellcode einfach zu lesen und zu verstehen.
    Und wenn Du weißt, dass Du invoken musst, ist der Test sowieso "umsonst".
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Im Prinzip funktioniert Async-Await immer gleich. Schau' dir vielleicht mal diesen Link an, falls dir das nicht geläufig ist: stackoverflow.com/questions/14…en-to-use-async-and-await
    Deine asynchrone Methode gibt halt dann EndInvoke zurück. Halte ich aber an dieser Stelle wirklich für unnötig. Es wird ein Thread vom Threadpool verwendet, um die Sachen auszuführen. Ist zwar an sich eine an manchen Stellen brauchbare Verzahnung der Aufgaben, aber in den meisten Fällen, würde ich meinen, unnötig und mit geringem Aufwand nachträglich einbaubar.

    Ich dachte übrigens, dass Invoke zumindest bei Forms fehlschlägt, wenn man es aus dem gleichen Thread heraus aufruft.
    Und oben hab' ich wohl nicht genau genug geschaut. Es wurde ja isi.Invoke aufgerufen, nicht Invoke direkt. Wo da mein Kopf mal wieder war... :D

    Viele Grüße
    ~blaze~
    @'ErfinderDesRades
    Na ja, ich versuche, eine möglichst generische Methode zum Updaten der GUI in einem anderen Thread zu finden. Die mir bekannten Lösungen, auch Dein CrossThread-Ansatz aus dem TCPIP-Chat, scheinen mir irgendwie alle 'durch die Jacke in die Hose' zu sein.
    Im TCPIP-Chat habich was mit CrossThreads gemacht?

    Ich hätte jetzt an AsyncWorker gedacht, da gibts auch sone generische, statische Methode AsyncWorker.NotifyGui(Of T1, T2)(delegate, arg1, arg2,...).
    Da geht aber nix durch Jacke in Hose, sondern das deckte bereits 2010 alle Threading-Probleme ab (sogar noch ein klein mehr!), die neuerlich erst mit Async zu erschlagen sind.
    Und ist dabei besser designed (generische, streng typisiert, keine String-Smells) als was du da am Wickel hast.
    Wie gesagt: ist nur eine statische Methode - könnteste aussm Asyncworker auch auslagern, wennde den ansonsten nicht willst.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Nur, dass es am Ende intern überall auf genau dieses Konstrukt rausläuft, wird auch von async Verwendet. Also ist es irrelevant, wo man kapselt(naja nicht ganz), irwo passiert es so oder so.

    GetCallback könnte man ohne allozierung machen, geht aber sogar noch, da es nur beim Run ist. Und InvokeGui naja, muss ich glaub ich nicht viel sagen. Außerdem wäre für die generischen Parameter ein T4-Template schön.

    Die Frage ist also viel mehr braucht er das überhaupt und nen extra AsyncWorker dabei ist mMn wirklich nicht sinnvoll..
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Zugunsten der Architektur würde ich empfehlen, dass du eine Möglichkeit bietest, die bin deinem Tcp-Client empfangenen Daten abzufangen und zwar nicht über das Invoke-Zeug, das du gepostet hast, sondern ohne das. Wenn du bspw. ein Ereignis erstellst, in dem alle empfangenen Nachrichten nach außen weitergeleitet werden, kann das Handling jeder Abonnent für sich durchführen. Das ist wesentlich eleganter, als es vorher für alle Fälle zu verlangen.

    Viele Grüße
    ~blaze~

    jvbsl schrieb:

    Nur, dass es am Ende intern überall auf genau dieses Konstrukt rausläuft

    Ja, eben das haben meine Recherchen auch ergeben. Was an dieser Stelle ein T4-Template bewirken soll, erschliesst sich mir jedoch nicht.
    @ErfinderDesRades
    In AsyncWorker hast Du das Beispiel in C# vorgestellt, in VersuchsChat mit leistungsfähigem Server auch in VB
    @~blaze~
    Danke für den Hinweis. Wie jedoch oben schon erwähnt, such' ich keine Lösung für ein spezielles Problem, sondern eine möglichst generische Lösung für das Update einer GUI aus einem anderen Thread.

    Auslösend für diesen Thread war ja, dass ich im Rahmen meiner Recherchen auf den in Post#1 zitierten C#-Text gestossen bin, den ich nicht verstanden habe.

    Nach meinem jetzigen Kenntnisstand kommt die von @ErfinderDesRades in seinen o.a. vorgeschlagenen Beispielen einem generischen Weg am nächsten. Insbesondere die Typsicherheit hat da ihren eigenen Charme. Ich denke, ich kann dieses Topic schliessen. Herzlichen Dank für Eure Beiträge.
    es gibt doch in C# die ConcurrentQueque, mit der du Thread-übergreifend Daten bereit stellen kannst.

    Schau mal hier auf MSDN und SO.

    Schreibst halt dann von deinem Sub-Thread Daten da rein und prüfst in dem GUI-Thread ob Einträge drin sind und führst dem entsprechende Funktionen aus. Ist zwar hinterher kein Augenschmaus, aber Threadsafe ;)
    Dann kannst du das ganze ja als T4-Template schreiben, damit die Funktion "beliebig"(10 vlt.) viele Parameter haben kann, ohne dass du alles 10 mal schreibst.
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---

    us4711 schrieb:

    Update einer GUI aus einem anderen Thread
    heißt schon mal ganz klar, dass Du .InvokeRequired nicht abfragen musst, weil es immer true ist.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!