Dynamische Arbeitsroutine (Methode/Funktion) asynchron im modalen IsBusy-Dialog abarbeiten

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 42 Antworten in diesem Thema. Der letzte Beitrag () ist von TRiViUM.

    Dynamische Arbeitsroutine (Methode/Funktion) asynchron im modalen IsBusy-Dialog abarbeiten

    Guten Abend liebe Community,

    ich benötige bei einer Sache mal euer gebündeltes Schwarmwissen zum Thema synchrone Methode/Funktion asynchron aufrufen.

    Kurze Erklärung zu meinem Vorhaben:

    Vorweg, ich möchte versuchen den BackgroundWorker und die System.Threading.Thread-Klasse zu meiden...

    Ich habe eine "normale"/synchrone Methode (dauer ca. 10 Sek., kommuniziert mit nem USB-Gerät).
    Diese Funktion rufe ich aus dem UI-Thread auf und somit hängt die Anwendung, soweit so gut.

    Da der Benutzer im allgemeinen sehr ungeduldig ist weiß er nicht, was in den 10 Sekunden vor sich geht und er könnte nervös werden wird nervös.
    Aus diesem Grund wollte ich eine 2te Form anzeigen, welche sowas wie "Bitte warten" und eine Progressbar etc. anzeigt.

    Da sich diese Loading-Form als modaler Dialog verhalten soll, müsste ich sie mit ShowDialog aufrufen, was sie auch synchron macht.
    Somit kann ich die Loading-Form nicht mit ShowDialog anzeigen, dann meine Funktion aufrufen und anschließend die Loading-Form wieder schließen.
    Modal, weil ich genau das gleiche Verhalten wie bei ShowDialog möchte: Fenster im Vordergrund der Anwendung, Eingaben gehen erst nach Schließen des Fensters wieder.

    Ich könnte zwar einfach ein Label mit "Bitte warten" über alle Steuerelemente legen, aber ich möchte mich gern in die Thematik der asynchronen Programmierung weiter einarbeiten.

    Habe mir das hier mal verinnerlicht.

    Hier mal mein Code:

    Dieser Code stellt mir eine Erweiterungsmethode für die Form-Klasse bereit:

    C#-Quellcode

    1. public static Task<DialogResult> ShowDialogAsync(this Form self)
    2. {
    3. if (self == null)
    4. throw new ArgumentNullException( "self" );
    5. TaskCompletionSource<DialogResult> completion = new TaskCompletionSource<DialogResult>();
    6. var selfHandle = self.Handle;
    7. self.BeginInvoke( new Action( () => completion.SetResult( self.ShowDialog() ) ) );
    8. return completion.Task;
    9. }


    Und da, wo ich meine Methode aufrufe und die Loading-Form anzeigen möchte, mache ich folgendes:

    C#-Quellcode

    1. private async void asyncOperation()
    2. {
    3. frmLoading dialog = new frmLoading();
    4. await dialog.ShowDialogAsync();
    5. controller.SetMode( 2 ); // communication with usb device
    6. dialog.Close();
    7. }


    Vom Grundprinzip hab ich das Vorhaben verstanden, allerdings wird meine Methode erst nach beenden der Form ausgeführt, vermutlich nur ein Denkfehler.
    Oder komme ich so nicht zu meinem gewünschten Ergebnis, sondern muss eins der beiden doch in ein separaten Thread/Backgroundworker auslagern?

    Vielen Dank für eure Antworten! :saint:

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „TRiViUM“ ()

    Ich würde dein Vorhaben neu planen, folgendermassen:
    was du brauchst ist ein modaler IsBusy-Dialog, der verhindert, das der ungeduldige User zwischenzeitlich iwas anneres in der Anwendung herumklickst.
    Dieser IsBusy-Dialog muss ganz normal modal anzeigen, und muss einen Cancel-Button haben, wenn der User die asynchrone Aktion canceln will.
    Der IsBusy-Dialog muss ausserdem automatisch schliessen, wenn die asynchrone Aktion abgeschlossen ist.

    Also sollte am besten der IsBusy-Dialog in seinem Form_Shown-Event die Aktion Async starten, und wenn durchgelaufen sich selbst schliessen.
    Ausserdem muss für die Aktion Cancellation implementiert sein, sodass dann die Aktion beendet (und da sie dann ja durchgelaufen ist, schliesst damit automatisch auch der IsBusy-Dialog).

    (Von deine gezeigten Code-Snippets sehe ich grad nix, was für das von mir skizzierte brauchbar wäre.)
    @ErfinderDesRades
    Deine Formulierung trifft es besser, ein modaler IsBusy-Dialog, genau.
    Das, was du geschrieben hast, klingt sehr nach dem Einsatz von einem Backgroundworker.

    Ist soweit okay, nur stellt sich mir dann eine Architekturfrage:
    Wenn der IsBusy-Dialog selbst die gewünschte Arbeit starten soll, welche ja dynamisch ist (mal ne Methode, mal ne Funktion), muss ich dem IsBusy-Dialog zum einen die "Arbeits-Routine" (ggf. mit Parametern) mitteilen können und zum Anderen dann bei einer Funktion den Rückgabewert bekommen können.
    Damit wäre der IsBusy-Dialog universell einsetzbar.

    Wie genau mache ich das?
    Mir fällt der Begriff dazu nicht ein...wäre quasi das Gegenteil vom Callback, damit ich Arbeits-Routine bestimmen kann.

    Wäre das hier das, was ich dafür bräuchte?

    Dieser Beitrag wurde bereits 7 mal editiert, zuletzt von „TRiViUM“ ()

    TRiViUM schrieb:

    Wie genau mache ich das?
    Du instanziierst den Dialog, danach musst Du diese Instanz parametrieren, also dem Dialog per Properties alles erforderliche kund tun.
    Arbeitsroutinen übergibst Du per Delegate.
    Wenn das erfolgt ist, startest Du den Dialog.
    Wenn der Dialog was zu melden hat, sendet er an das Hauptprogramm ein Event.
    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!

    TRiViUM schrieb:

    klingt sehr nach dem Einsatz von einem Backgroundworker.
    Nein nein nein - bitte nicht!
    Vergiss Backgroundworker - wenn du den jetzt einsetzt, dann tu ich dich deine Post von nun an blocken.
    Die Geschichte mit Async + Cancelation ist hier erläutert: codeproject.com/Articles/10296…ithout-any-additional-Lin
    Dass der IsBusy-Dialog die Aktion starten muss und nicht der eigliche Aufrufer ist allerdings ein Erschwernis, was dort nicht behandelt wird.
    Ich denke auch, das müsste mit Delegaten gehen, habs aber selbst noch nie gemacht.

    RodFromGermany schrieb:

    Arbeitsroutinen übergibst Du per Delegate.
    Gibt es derart dynamische Delegaten, damit ich nicht verschiedene Delegaten mit verschiedener Anzahl an Überladungen programmieren muss?

    ErfinderDesRades schrieb:

    Nein nein nein - bitte nicht!
    Vergiss Backgroundworker - wenn du den jetzt einsetzt, dann tu ich dich deine Post von nun an blocken.
    Okay, okay...ich hatte ja eh vor, den Backgroundworker zu meiden, meinte ja nur dasses danach klingt wegen cancelation usw...
    Ich denke aber, dass Dein Link ein Grundgerüst dafür ist, da hier auch Delegaten/Funktionen übergeben werden können.
    Ich experimentiere mal ein wenig damit herum, vielen Dank!
    @TRiViUM Ich denke nicht.
    Wenn die Prozeduren und die Parameter in der aufrufenden Instanz vorhanden sind, löse doch vom Dialog aus ein Event aus, und der Abbonent kümmert sich darum.
    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!
    @RodFromGermany:
    Das sind sie leider nicht.
    Die aufrufende Instanz hält Instanzen von weiteren Klassen, wo sich die Routinen drin befinden.
    Diese möchte ich auch ungern dort heraus verfrachten.
    Desweiteren müsste ich mir dann zusätzlich inner Variable merken, was ich machen möchte, wenn ich übers Event benachrichtigt werde.

    Der Gedanke vom Ablauf wäre folgender:
    1) Aufrufende Instanz bestückt IsBusy-Dialog mit er Arbeitsroutine (Funktion/Methode und ggf. dessen Parametern), welche sich nicht unbedingt in der aufrufenden Instanz befindet.
    2) Die aufrufende Instanz zeigt anschließend den Dialog modal mit ShowDialog().
    3) Im Shown-Event des IsBusy-Dialogs wird nun die bestückte Arbeitsroutine asynchron abgearbeitet, ggf. ein Rückgabewert als Property gesetzt und anschließend schließt sich der IsBusy-Dialog selbst.
    4) Die aufzurufende Instanz kann nun den Rückgabewert abholen oder auch nicht.
    5) Der Benutzer sieht, dass die Anwendung für die Zeit des Arbeitens nicht eingefroren ist.

    Ich dachte, das Schlüsselwort für dynamische Funktionen bzw. Funktionen mit dynamischen Parametern wäre hier

    C#-Quellcode

    1. public Func<dynamic, dynamic> Function { get; set; }
    gewesen, allerdings kann ich mich nicht schlau drüber lesen, wie ich diese Property von außen korrekt bestücke ?(

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „TRiViUM“ ()

    @TRiViUM Probier das ggf. über ein interface oder gib den Delegaten in einer separaten Klasse rüber, je nach Instanz kannst Du die Klasse mit Deinen Informationen füttern.
    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!

    TRiViUM schrieb:

    Mit nem Interface
    Da kannst Du doch Deine Delegaten reinpacken und dem Dialog z.B. per Index sagen, welchen er nehmen soll.
    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!
    @RodFromGermany Grundsätzlich ja, aber ich dachte, es gäbe etwas dynamisches, sodass ich nur einen Delegaten brauche (Programmierer sind ja grundsätzlich faul und wollen wenig tippen).

    Also eine Art Delegaten, dem ich eine Methode als auch eine Funktion zuweisen kann, die dann ggf. auch noch dynamische Parameter und Rückgabewerte haben.
    Der Aufrufer weiß ja, was er wie bestücken muss und was am Ende bei raus kommt...

    Aber wenn es soetwas in der Form nicht gibt, komme ich wohl nicht drum herum.
    @TRiViUM Oder so was wie bei String.Format(), wo Du beliebige Parameter anhängen kannst:

    VB.NET-Quellcode

    1. Private Sub Format(ByVal format As String, ParamArray args As Object())
    2. ' ...
    3. End Sub
    4. ' bzw.
    5. Private Sub MyProcedure(ParamArray args As Object())
    6. End Sub
    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!
    @RodFromGermany Jau, sowas kommt meiner Vorstellung schon recht nah.
    Hab momentan folgendes:

    C#-Quellcode

    1. using System;
    2. using System.Windows.Forms;
    3. using System.Threading.Tasks;
    4. namespace Software.Ui.Dialogs
    5. {
    6. public partial class frmIsBusy: Form
    7. {
    8. public frmIsBusy()
    9. {
    10. InitializeComponent();
    11. }
    12. // Muss vom Aufrufer beschrieben werden
    13. public Func<dynamic> WorkingRoutine { get; set; }
    14. public object[] Parameters { get; set; }
    15. // Kann vom Aufrufer abgeholt werden
    16. public object ReturnValue { get; private set; }
    17. // Momentan noch interne Funktion zum debuggen und zuweisen
    18. private string function()
    19. {
    20. System.Threading.Thread.Sleep( 10000 );
    21. return "1";
    22. }
    23. // async muss hinzugefügt werden
    24. private async void frmIsBusy_Shown(object sender, EventArgs e)
    25. {
    26. // Allocate the working routine
    27. WorkingRoutine = function;
    28. // start working routine
    29. ReturnValue = await Task.Run( () => WorkingRoutine.DynamicInvoke( Parameters ) );
    30. // when finish, close this form
    31. Close();
    32. }
    33. }
    34. }


    Das funktioniert auch erstmal so, wie gewünscht und auch der Rückgabewert wird korrekt abgelegt.

    Allerdings meckert der Compiler, wenn ich der Funktion "function" jetzt noch Parameter hinzufügen möchte bzw. ich weiß nicht, wie ich solch eine Funktion der Property "WorkingRoutine" korrekt zuweisen soll ?(

    TRiViUM schrieb:

    Allerdings meckert der Compiler, wenn ich der Funktion "function" jetzt noch Parameter hinzufügen möchte
    Ich hab das im Studio 2013 mit dem Framework 4.6.1 compiliert, das sollte gehen.
    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!

    RodFromGermany schrieb:

    das sollte gehen

    Den von mir gepostete Code kann ich so auch kompilieren, aber wenn ich jetzt z.B. die Zeile

    C#-Quellcode

    1. private string function()

    abändere zu

    C#-Quellcode

    1. private string function(int i)
    dann passt was nicht.

    Ist ja eigentlich auch logisch, die Funktion will mit Parametern gefüttert werden, nur probiere ich hier schon alles mögliche durch und weiß nicht, wie ich es das mache :S
    Dr. Google hat dazu komischerweise keine mir passende Lösung dazu...

    Edit:
    Glaub ich habs, zumindest für Funktionen:

    C#-Quellcode

    1. ​using System;
    2. using System.Windows.Forms;
    3. using System.Threading.Tasks;
    4. namespace Software.Ui.Dialogs
    5. {
    6. public partial class frmIsBusy : Form
    7. {
    8. public frmIsBusy()
    9. {
    10. InitializeComponent();
    11. }
    12. // Muss vom Aufrufer beschrieben werden
    13. public Func<dynamic> WorkingRoutine { get; set; }
    14. // Kann vom Aufrufer abgeholt werden
    15. public object ReturnValue { get; private set; }
    16. private string function(int i, int j)
    17. {
    18. System.Threading.Thread.Sleep( 10000 );
    19. return i.ToString() + j.ToString() ;
    20. }
    21. // async muss hinzugefügt werden
    22. private async void frmIsBusy_Shown(object sender, EventArgs e)
    23. {
    24. // diese Zuweisung muss vom Aufrufenden kommen
    25. WorkingRoutine = () => function(2, 3);
    26. // Funktion asynchron ausführen
    27. ReturnValue = await Task.Run( () => WorkingRoutine.Invoke() );
    28. // Form schließen
    29. Close();
    30. }
    31. }
    32. }

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

    @TRiViUM Alles eine Deklarationsfrage:
    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.Windows.Forms;
    3. using System.Threading.Tasks;
    4. namespace WindowsFormsApplication1
    5. {
    6. public partial class frmIsBusy : Form
    7. {
    8. public frmIsBusy()
    9. {
    10. this.InitializeComponent();
    11. }
    12. // Muss vom Aufrufer beschrieben werden
    13. public Func<object[], object> WorkingRoutine { get; set; }
    14. public object[] Parameters { get; set; }
    15. // Kann vom Aufrufer abgeholt werden
    16. public object ReturnValue { get; private set; }
    17. // Momentan noch interne Funktion zum debuggen und zuweisen
    18. private object function(object[] obj)
    19. {
    20. System.Threading.Thread.Sleep(1000);
    21. return "1";
    22. }
    23. // async muss hinzugefügt werden
    24. private async void frmIsBusy_Shown(object sender, EventArgs e)
    25. {
    26. // Allocate the working routine
    27. WorkingRoutine = function;
    28. // start working routine
    29. ReturnValue = await Task.Run(() => WorkingRoutine.DynamicInvoke(Parameters));
    30. // when finish, close this form
    31. Close();
    32. }
    33. }
    34. }
    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!
    @RodFromGermany
    Korrekt, allerdings kann ich die Funktion "function" nicht im Voraus bestimmen, da diese ja von außerhalb zugewiesen wird.

    Ich habe jetzt etwas hin bekommen, was sowohl mit ner Funktion als auch mit ner Methode funktioniert, mit oder ohne Parameter:

    C#-Quellcode

    1. using System;
    2. using System.Windows.Forms;
    3. using System.Threading.Tasks;
    4. namespace Software.Ui.Dialogs
    5. {
    6. public partial class frmIsBusy: Form
    7. {
    8. public frmIsBusy()
    9. {
    10. InitializeComponent();
    11. }
    12. private Func<dynamic> workingFunction;
    13. private Action workingMethod;
    14. public object ReturnValue { get; private set; }
    15. // async muss hinzugefügt werden
    16. private async void frmIsBusy_Shown(object sender, EventArgs e)
    17. {
    18. // Funktion asynchron ausführen
    19. if( workingFunction != null )
    20. ReturnValue = await Task.Run( () => workingFunction.Invoke() );
    21. else if( workingMethod != null )
    22. await Task.Run( () => workingMethod.Invoke() );
    23. // Form schließen
    24. Close();
    25. }
    26. public void AllocateWorkingRoutine( Func<dynamic> function )
    27. {
    28. workingFunction = () => function();
    29. }
    30. public void AllocateWorkingRoutine(Action method)
    31. {
    32. workingMethod = () => method();
    33. }
    34. }
    35. }


    Und der Aufruf geht dann so:

    C#-Quellcode

    1. private void test()
    2. {
    3. frmIsBusy dialog = new frmIsBusy();
    4. //dialog.AllocateWorkingRoutine( () => debugMethod("test") ); // Überladung für Methode
    5. dialog.AllocateWorkingRoutine( () => debugFunction( 123 ) ); // Überladung für Funktion
    6. dialog.ShowDialog();
    7. object o = dialog.ReturnValue; // bei Funktionsaufruf zugewiesen, andernfalls NULL
    8. }
    9. private int debugFunction( int i)
    10. {
    11. System.Threading.Thread.Sleep(5000);
    12. return i;
    13. }
    14. private void debugMethod( string x)
    15. {
    16. System.Threading.Thread.Sleep( 5000 );
    17. }


    Das geht bestimmt noch schöner, und cancelation wird ja auch noch nicht unterstützt, wobei man das hiermit hinbekommen sollte:
    codeproject.com/Articles/10296…ithout-any-additional-Lin

    Vielen Dank für Eure Hilfe! :thumbup:

    Dieser Beitrag wurde bereits 8 mal editiert, zuletzt von „TRiViUM“ ()

    mal bischen vereinfacht:

    C#-Quellcode

    1. public partial class frmIsBusy : Form {
    2. public frmIsBusy() { InitializeComponent(); }
    3. private Func<dynamic> workingFunction;
    4. private Action workingMethod;
    5. public object ReturnValue { get; private set; }
    6. // async muss hinzugefügt werden
    7. private async void frmIsBusy_Shown(object sender, EventArgs e) {
    8. // Funktion asynchron ausführen
    9. if (workingFunction != null) ReturnValue = await Task.Run(workingFunction);
    10. else if (workingMethod != null) await Task.Run(workingMethod);
    11. // Form schließen
    12. Close();
    13. }
    14. public void AllocateWorkingRoutine(Func<dynamic> function) { workingFunction = function; }
    15. public void AllocateWorkingRoutine(Action method) { workingMethod = method; }
    16. }
    Sowas:

    C#-Quellcode

    1. workingFunction = () => function();
    erzeugt eine neue anonyme Methode, die die übergebene Methode aufruft.
    Kann man sich sparen, wenn die Signatur der übergebenen Methode eh schon passt.
    Also vorher hattest du eine anonyme Methode, die eine anonyme Methode aufrief, die eine anonyme Methode aufrief, die die Methode aufrief, die eiglich aufgerufen werden sollte.
    Nun wird die Methode, die eiglich aufgerufen werden sollte, gleich aufgerufen.

    PS: die Kommentare können auch weg - erklären nix.

    ErfinderDesRades schrieb:

    Also vorher hattest du eine anonyme Methode, die eine anonyme Methode aufrief, die eine anonyme Methode aufrief, die die Methode aufrief, die eiglich aufgerufen werden sollte.
    Uiuiui, dass hatte ich ganz vergessen noch abzuändern, nachdem ich beim Herumexperimentieren auf die Lösung mit mit der Methode public void AllocateWorkingRoutine gekommen bin. Danke für den Hinweis, ist mir nämlich nicht mehr aufgefallen :thumbsup: