Dialoge in den unbekannten Main-Thread invoken

  • VB.NET

Es gibt 13 Antworten in diesem Thema. Der letzte Beitrag () ist von nikeee13.

    Dialoge in den unbekannten Main-Thread invoken

    1. Beitrag im neuen Windows Forms-Unterforum. :thumbsup:
    Moin Leute.
    Ich bin da an einem "Makro-Interpreter", wo ein Makro in einem Json-Format vorliegt und von einem entsprechenden Programm abgearbeitet wird. Dies dient zur Automatisierung von Messaufgaben, die als "Messprimitive" implemeniert sind / werden.
    Eine solche wäre z.B. "Selektiere ein RS232-Port" aus einer ComboBox. Dazu gibt es einen Dialog, der die vorhandenen Ports auflistet und man ein Port auswählen kann. Fertig.
    Dieser modaler Dialog muss im Hauptfenster-Thread ablaufen, damit er sich wie ein normaler modaler Dialog des Programms verhält (in Vordergrund bleiben usw.).
    Für den Ablauf sollte es nicht nötig sein, dass die Makroelement-Implementationen Kenntnis vom Hauptfenster haben.
    Problem:
    Wie invoke ich einen Dialog in das (unbekannte) Hauptfenster?
    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!
    über Application.OpenForms(0)

    es gibt weitere Möglichkeiten: Control.Invoke() vs. .BeginInvoke()

    ich würds türlich gleich "richtig" machen: AsyncWorker - CodeProject - damit kann man auch Paramter typisiert transferieren, und allerlei annerem Threading-Unbill ist vorgebeugt.

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

    Moin,

    das (unbekannte) Hauptfenster
    Daraus schließe ich, dass es keine Variable gibt, über die man mit dem Fenster sprechen kann. Wenn das so ist, wäre meine Idee eine statische Variable in einer separaten Klasse zu verwenden, in die sich das Fenster im Load-Event einträgt.
    So in etwa

    VB.NET-Quellcode

    1. Public Class SeparateKlasse
    2. Public Shared Property Hauptfenster As Form
    3. End Class
    4. Public Class Hauptfenster
    5. Inherits Form
    6. Private Sub Hautptfenster_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
    7. SeparateKlasse.Hauptfenster = Me
    8. End Sub
    9. End Class

    Mit freundlichen Grüßen,
    Thunderbolt
    @ErfinderDesRades:: Jou, das war es.
    Mit einer Form2 mit einem OK- und einem Cancel-Button sieht das ganze so aus.
    @(All \ ErfinderDesRades): Gugt Ihr hier.
    Invoke

    VB.NET-Quellcode

    1. Private myThread As System.Threading.Thread
    2. Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    3. myThread = New System.Threading.Thread(AddressOf Run)
    4. myThread.Start()
    5. End Sub
    6. Private Sub Run()
    7. Using dlg As New Form2
    8. Dim isOk As Boolean = CBool(Application.OpenForms(0).Invoke(Function() _
    9. dlg.ShowDialog = Windows.Forms.DialogResult.OK _
    10. ))
    11. If Not isOk Then
    12. MessageBox.Show("Cancel")
    13. Return
    14. End If
    15. End Using
    16. MessageBox.Show("OK")
    17. End Sub
    @timmi31061:: In diesem Moment ist das Hauptfenster ja bekannt, denn ich müsste es über seine Instanz aus aufrufen, was ich vermeiden will.
    Irgendwann sollen die Aufrufe auch aus einer DLL heraus gemacht werden können, und ich möchte da keine Instanz reinreichen.
    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!
    Kannst du C# lesen? Dies ist wohl die beste Methode:

    C-Quellcode

    1. if (RequestCompleted == null) return;
    2. foreach (var @delegate in RequestCompleted.GetInvocationList())
    3. {
    4. var del = (RequestCompletedEventHandler) @delegate;
    5. if (del.Target is Control && ((Control) del.Target).InvokeRequired)
    6. ((Control) del.Target).Invoke(del, this, new RequestCompletedEventArgs {Stream = resultStream});
    7. else
    8. del(this, new RequestCompletedEventArgs {Stream = resultStream});
    9. }
    nein, das halte ich ühaupt nicht für die beste Methode - warum soll dieses Auseinander-Gedrösel des Delegaten, und Gesuche nach einem Control besser sein?
    Das failt etwa, wenn das Delegate.Target kein Control ist.
    Hingegen bei Application.OpenForms(0) wissen wir sicher, dasses ein Control im Mainthread ist.

    ThuCommix schrieb:

    Kannst du C# lesen?
    ein wenig ;)
    Spoiler anzeigen

    C-Quellcode

    1. public override void runIt()
    2. {
    3. using (dialogs.DlgComboBox dlg = new dialogs.DlgComboBox())
    4. {
    5. dlg.Title = this.Title;
    6. dlg.Prompt = this.Prompt;
    7. dlg.SelectFrom = this.SelectFrom;
    8. dlg.Select = this.Select;
    9. System.Windows.Forms.DialogResult retval = System.Windows.Forms.DialogResult.Cancel;
    10. Action act = () => retval = dlg.ShowDialog();
    11. System.Windows.Forms.Application.OpenForms[0].Invoke(act);
    12. if (retval != System.Windows.Forms.DialogResult.OK)
    13. {
    14. this.setError("Button "Cancel" pressed");
    15. return;
    16. }
    17. this.Input = dlg.Input;
    18. }
    19. }
    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!
    Auch, wenn dieser Lösungsansastz zu funktionieren scheint, wirkt er auf mich irgendwie nicht ganz koscher. Wie ist das, wenn man ein Library-Entwickler ist? Da kann man über die zukünftige Programmumgebung schlecht etwas sagen. Wenn ich mich nicht irre, ist es allerdings egal, welchen Index man verwendet, weil es ja nur darauf ankommt, dass man in den GUI-Thread invoked. Als Komponentenentwickler würde ich mir aber nicht erlauben, einfach in irgendwelche Threads zu invoken. IMO sollte das der Aufrufer regeln.

    Nach etwas Überlegen ist mir eingefallen, dass man mit TAP (async/await) (oder simples Benutzen von Tasks?) das Problem wohl auch lösen könnte:

    VB.NET-Quellcode

    1. ' GUI-Thread (bzw Anfangs-Thread)
    2. Dim ergebnis = Await AsynchroneOperation() ' wird ggf. in Nebenthread ausgeführt
    3. ' GUI-Thread (bzw Anfangs-Thread)
    4. ' Hier könnte der Dialog aufgerufen werden
    5. ergebnis = Await AsynchroneOperation().ConfigureAwait(False) ' wird ggf. in Nebenthread ausgeführt
    6. ' Unbekannter Thread, da Context Switching explizit ausgestellt wurde
    Da du aber mit 2010 arbeitest, müsstest du das in eine Task-Only-Variante umschreiben (FW 4.0 vorausgesetzt).

    Hat es einen Grund, warum du der Dialogbox nicht via Konstruktor sagst, zu welcher Form sie gehört? ;)
    Von meinem iPhone gesendet
    Naja, das Problem ist einfach nicht schön lösbar:
    Es muss einen gemeinsamen Gui-Thread geben, und daher eine global verfügbare SynchronisizeContext-Instanz, die Aufrufe in diesen Thread delegieren kann.

    Und eine globale Instanz ist in OOP numal hässlich, aber geht nicht anners (wie das Await-Konstrukt aushelfen soll kapiere ich grad nicht).

    Und mitte Application-Instanz ist in WinForms doch schonmal eine globale Instanz gegeben, also halte ichs zumindest für schöner, die halt zu benutzen - ehe ich hergehe, und noch eine programmiere.

    ErfinderDesRades schrieb:

    wie das Await-Konstrukt aushelfen soll kapiere ich grad nicht

    Das Await-Konstrukt sollte das Problem dahingehend lösen, dass Invoking garnicht erst benötigt wird.

    Ich habs jetzt nicht ausprobiert, aber so in etwa sollte das gehen:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Sub IchBinImMainThread()
    2. MachWas()
    3. End Sub
    4. Async Sub MachWas()
    5. Trace.WriteLine("Mache jetzt was")
    6. Dim ergebnis = Await AsynchroneOperation() ' wird ggf. in Nebenthread ausgeführt
    7. ' Hier irgendwas im Main-Thread machen
    8. Using dlg As New DialogBox()
    9. dlg.Options = ergebnis
    10. if dlg.ShowDialog() <> DialogResult.OK Then
    11. Trace.WriteLine("Jetzt nicht mehr")
    12. Return
    13. End if
    14. End Using
    15. Trace.WriteLine("Ich mach dann mal weiter...")
    16. Await IrgendeineAndereOperation()
    17. Trace.WriteLine("Bin jetzt übrigens fertig.")
    18. End Sub
    19. Function AsynchroneOperation() As Task(Of List(Of String))
    20. return New Task(Of List(Of String))(Function()
    21. ' Irgendwas hier machen.
    22. ' Wird später in einem anderen Thread ausgeführt
    23. ' Hier eventuell schöneres Threading verwenden (-> Implementation Guidelines vom TAP?)
    24. Thread.Sleep(9001)
    25. return new List(Of String)({"Lol", "Lel"})
    26. End Function)
    27. End Function
    28. Function IrgendeineAndereOperation() As Task
    29. return New Task(Sub()
    30. ' ....
    31. End Sub)
    32. End Function



    ErfinderDesRades schrieb:

    Und mitte Application-Instanz ist in WinForms doch schonmal eine globale Instanz gegeben
    Ist sie es auch, wenn ich in 'ner ASP.NET-, WPF- oder Konsolenanwendung bin? Ich wäre mir da nicht so sicher, kann aber durchaus sein.

    Wenn ich (achtung, IMO) eine Library veröffentliche, die ansich nichts mit GUI zu tun hat, sollte sie auch unabhängig vom GUI-System laufen.
    Von meinem iPhone gesendet

    nikeee13 schrieb:

    Das Await-Konstrukt sollte das Problem dahingehend lösen, dass Invoking garnicht erst benötigt wird.
    ich kenn mich mit Await nicht aus, aber einige externe Objekte feuern doch nunmal ihre Events im Nebenthread: FilesystemWatcher, SerialPort und so.

    nikeee13 schrieb:

    Ist sie es auch, wenn ich in 'ner ASP.NET-, WPF- oder Konsolenanwendung bin?
    nein - ist nicht. Application.Openforms geht nur in Winforms.
    In anderen Zusammenhängen kann man aus dem aktuellen Thread ein SynchronisationContext erzeugt werden, und iwie anners global verfügbar gemacht werden.
    Dabei tritt das Problem auf, zu gewährleisten, dasses wirklich der Gui-Thread ist, aus dem der SynchronisationContext erzeugt wird.

    Also das muss dann beim Startup extra angestoßen werden, während bei Application.OpenForms sich das AnwendungsFramework schon drum gekümmert hat.
    Ein anneres Problem sind Threads, die invoken wollen, wenn das MainForm bereits geschlossen wurde.
    Mit Application.OpenForms kannste das abfangen, dann ist .Count nämlich 0 :)

    ErfinderDesRades schrieb:

    einige externe Objekte feuern doch nunmal ihre Events im Nebenthread
    Richtig. Das ist so, weil der Aufrufer die Threadsynchronisation übernehmen bzw. bestimmen sollte (siehe mein Post weiter oben).

    Wenn man nicht die Forminstanz oder die ISynchronizeInvoke-Instanz übergeben will, kann der Aufrufer es wenigstens darüber steuern, wie er die Zielmethode aufruft. Das ist IMO besser als einfach in den erstbesten Thread zu invoken. Genauer habe ich damit aber noch nicht beschäftigt.

    Gut, die von mir gezeigt TAP-Methode würde voraussetzen, dass sie aus dem GUI-Thread gecallt wird, denn sonst wird das mit der DialogBox nichts. Die OpenForms-Methode würde unabhängig vom Caller-Thread funktionieren, funktioniert dafür aber nur unter Winforms (Portierungsaufwand notwendig) und könnte auch mal fehlschlagen.

    Edit:
    In diesem konkreten Anwendungsfall wird es wohl auf die OpenForms-Methode hinauslaufen, da es ja um VB2010 geht. Ich weiß jetzt auch nicht, ob das AsyncTargetingPack auch da schon ging. Aber selbst wenn, lohnt sich die Referenz auf eine dicke Library für diese kleine Anwendung wohl eher nicht.
    Von meinem iPhone gesendet

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