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.

    mMn ist der untypisierte Rückgabewert und die explizite Unterscheidung zwischen einer Workerfunction und -action nicht optimal. Eine denkbare Alternative wäre die Rückgabewerte - soweit vorhanden - als Referenzparameter an die jeweilige Methode zu übergeben.

    kurz skizziert:

    C#-Quellcode

    1. public class Worker
    2. {
    3. private Action m_WorkingFunction;
    4. public Worker(Action workingFunction)
    5. {
    6. m_WorkingFunction = workingFunction;
    7. this.Work();
    8. }
    9. private async void Work()
    10. {
    11. await Task.Run(m_WorkingFunction);
    12. }
    13. }


    Aufruf:

    C#-Quellcode

    1. <Test>
    2. private void Test()
    3. {
    4. int result = 0;
    5. new Worker(new Action(() => SampleMethod(5, out result)));
    6. Assert.AreEquals(10, result)
    7. }
    8. private static void SampleMethod(int SampleIn, out int SampleOut)
    9. {
    10. SampleOut = SampleIn * 2;
    11. }

    ChristianT. schrieb:

    der untypisierte Rückgabewert und die explizite Unterscheidung zwischen einer Workerfunction und -action

    Das ist grundsätzlich etwas unschön, zugegeben.
    Allerdings ist es so universell einsetzbar, denn wenn eine Funktion aus ner Klassenbibliothek kommt, geht das so schon nicht mehr.
    Man könnte höchstens noch nen Typ-Parameter angeben, in den dann das ReturnValue gecastet wird, aber auch den kennt der Aufrufer eigentlich und kann auch selbst direkt casten, so mache ich es zumindest momentan.

    BTW, dieser Code funktioniert sogar auch mit Funktionen, die Referenzen als Parameter haben.

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

    ChristianT. schrieb:

    untypisierte Rückgabewert ... nicht optimal
    Nicht schön - seh ich auch so.
    Aber der Vorgang muss asynchron aus einem Form aufgerufen werden, und ein Form kann eine generisch typisierte Rückgabewert-Property nicht bereitstellen.
    Da müsste man ganz anners konstruieren, vermutlich mit einem zusätzlichen Callback-Delegaten.
    Wäre dann schön typisiert, aber glaub deutlich komplizierter, und auch komplizierter zu benutzen.
    Hallo, ich habe euch mal im Anhang ein Beispielprojekt erstellt, wie man das Problem ohne dynamic (das ist böse) lösen kann.
    Es ist nur ein Prototyp - falls es Probleme gibt/nicht läuft, sagt Bescheid.

    Alles, was du brauchst ist eine Form (BusyDialogForm) und der Code hier:

    C#-Quellcode

    1. public static class BusyDialog
    2. {
    3. public static async Task ShowAsync(Func<Task> operation)
    4. {
    5. // We can easily reuse the ShowAsync method with a result.
    6. // To make it simple, just introduce a dummy result.
    7. await ShowAsync<object>(async () =>
    8. {
    9. await operation();
    10. return null;
    11. });
    12. }
    13. public static async Task<TResult> ShowAsync<TResult>(Func<Task<TResult>> operation)
    14. {
    15. var busyDialog = new BusyDialogForm();
    16. _ = busyDialog.Handle;
    17. busyDialog.BeginInvoke(new Action(() => busyDialog.ShowDialog()));
    18. try
    19. {
    20. return await operation();
    21. }
    22. finally
    23. {
    24. // Important: `operation` can obviously throw.
    25. // We should never forget to close the dialog here.
    26. try
    27. {
    28. busyDialog.Invoke(new Action(() => busyDialog.Close()));
    29. }
    30. catch (Exception ex) when (ex is InvalidOperationException || ex is ObjectDisposedException)
    31. {
    32. // Closing can fail if the Form hasn't been shown yet.
    33. // This can happen when `operation` completes incredibly fast.
    34. }
    35. }
    36. }
    37. }


    Wichtig: :!:
    • Die BusyDialogForm benötigt keine einzige Zeile Code.
    • Die Methoden ShowAsync() habe ich in eine statische Klasse ausgelagert. Somit funktioniert der dialog genauso wie MessageBox.Show(...). Man kann sie natürlich auch an jede anderen Stelle einfügen, z.B. in der BusyDialogForm. Würde ich aber nicht empfehlen.
    • Durch die Aufspaltung haben wir eine schöne Trennung der Funktionen. Die ShowAsync() Methoden oben funktionieren mit jeder Art von Form. Und die BusyDialogForm muss nicht im Load-Event die asynchrone Operation starten (das ist besonders wichtig/schön).
    • ShowAsync ist auf Funcs aufgebaut. Da man hier Lambdas nutzen kann/soll, kann man durch Variable-Capturing beliebig viele Parameter an die Asynchrone Methode weitergeben.

    Benutzen kann man den Spaß z.B. so (Code aus Main Form):

    C#-Quellcode

    1. private async void btnLongOperation_Click(object sender, EventArgs e)
    2. {
    3. await BusyDialog.ShowAsync(LongOperation);
    4. MessageBox.Show("Done!");
    5. }
    6. private async void btnLongOperationWithParameter_Click(object sender, EventArgs e)
    7. {
    8. await BusyDialog.ShowAsync(() => LongOperationWithParameter("Hello", "World"));
    9. MessageBox.Show("Done!");
    10. }
    11. private async void btnLongOperationWithParameterAndResult_Click(object sender, EventArgs e)
    12. {
    13. var result = await BusyDialog.ShowAsync(() => LongOperationWithResultAndParameter(42));
    14. MessageBox.Show($"Done! The result was {result}");
    15. }
    16. private static async Task LongOperation()
    17. {
    18. await Task.Delay(5000);
    19. }
    20. private static async Task LongOperationWithParameter(string first, string second)
    21. {
    22. await Task.Delay(5000);
    23. Debug.Write($"{first} {second}"); // Just use the params somehow
    24. }
    25. private static async Task<int> LongOperationWithResultAndParameter(int theResult)
    26. {
    27. await Task.Delay(5000);
    28. return theResult;
    29. }

    Dateien
    @shad Hallo & Danke für Deine Zeit.
    Warum ist dynamic böse?
    Hatte zugegebenermaßen noch nix mit Tasks/Actions/Func gemacht, aber das dynamic schien mir dafür wie gemacht.
    Da das hier aber eine große Community ist bin ich froh, euer Wissen sammeln zu können :saint:

    Habe mehrere Fragen zu Deinem Code.

    shad schrieb:

    C#-Quellcode

    1. _ = busyDialog.Handle;
    Wozu genau ist diese Zeile notwendig?


    shad schrieb:

    C#-Quellcode

    1. busyDialog.Invoke(new Action(() => busyDialog.Close()));
    Und warum muss man hier auch eine anonyme Methode erstellen, die dann invoked wird, um den Invoker zu schließen?


    shad schrieb:

    C#-Quellcode

    1. private static async Task LongOperationWithParameter(string first, string second)
    2. {
    3. await Task.Delay(5000);
    4. Debug.Write($"{first} {second}"); // Just use the params somehow
    5. }
    Das heißt, ich müsste für jede Arbeitsroutine, welche ich mit dem IsBusy-Dialog abarbeiten möchte, nur noch in eine weiteren Funktion/Methode kapseln, die ich mit dem Schlüsselword async ausschmücke?

    shad schrieb:

    Somit funktioniert der dialog genauso wie MessageBox.Show(...)
    Vielleicht könnte man ne Erweiterungsmethode schreiben, damit man das Form direkt über busyDialogInstance.ShowDialogAsync() aufrufen könnte...
    Hi @TRiViUM,

    ich gehe die Fragen mal der Reihe nach durch:

    TRiViUM schrieb:

    Warum ist dynamic böse?

    Dazu gibt es viele mögliche Antworten - die wichtigste ist Folgende:
    dynamic erlaubt es dir, Code zu schreiben, der nicht vom Compiler geprüft wird. Stattdessen wird Code mit dynamic erst evaluiert, sobald das Programm läuft. Das hat einen großen Nachteil: Wenn der Code falsch ist (und das passiert schnell, beispielsweise könntest du an eine dynamic Func<...> aus Versehen zu viele Parameter übergeben. Wie gesagt, der Compiler meckert hier nicht) bricht dein Programm erst zur Laufzeit mit einem Fehler ab. Ohne dynamic bekommst du solche Fehler schon beim Kompilieren gemeldet.
    Im Prinzip schaltest du durch dynamic quasi hilfreiche Fehlermeldungen ab, bzw. anders formuliert, du lässt dir nicht vom Compiler helfen, richtigen Code zu schreiben. Außerdem merkst du auch schnell, dass dir deine Tools nicht mehr so gut helfen können, Code zu schreiben. Visual Studio's IntelliSense kann mit dynamic beispielsweise nicht wirklich gut umgehen und dir dementsprechend schlecht Vorschläge generieren.

    Es gibt natürlich Situationen, in denen dynamic sinnvoll ist, allerdings postuliere ich mal, dass du dynamic in 99,9% der Fälle durch ein typsicheres, compiler-geprüftes Konstrukt ersetzen kannst. Und wie gesagt, das ist immer besser, da du so bereits beim Erstellen des Programms siehst, dass etwas nicht passt. Der Compiler ist dein Freund! :)

    Falls du interessiert bist: dynamic wurde von MS damals hauptsächlich eingeführt, um Interop mit anderen, nicht-typisierten Sprachen wie z.B. Phython zu ermöglichen - Stichwort DLR: docs.microsoft.com/en-us/dotne…#dynamic-language-runtime
    dynamic wurde nicht primär eingeführt, um es Programmierern zu ermöglichen, typsicheren Code über Bord zu werfen.

    TRiViUM schrieb:


    C#-Quellcode

    1. _ = busyDialog.Handle;


    Wozu genau ist diese Zeile notwendig?

    C#-Quellcode

    1. busyDialog.Invoke(new Action(() => busyDialog.Close()));

    Und warum muss man hier auch eine anonyme Methode erstellen, die dann invoked wird, um den Invoker zu schließen?


    Zu den beiden Punkten: Mach mal die Solution auf und ändere die Zeilen/entferne sie. Du wirst merken, dass du ohne diesen beiden in 2 verschiedene Exceptions läuft.

    In deinem ersten Post hast du sogar einen Beispielcode zum asynchronen ShowDialog() gepostet:

    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. }


    In Zeile 7 ist genau das gleiche zu sehen: var selfHandle = self.Handle - ich habe es quasi nur übernommen. Man braucht die Zeile, da WinForms einen BeginInvoke(...) nur erlaubt, wenn vorher auf das Handle einer Form zugegriffen wurde. Ohne dieser Zeile wirft BeginInvoke(...) eine Exception. Der Unterstrich am Anfang _ = busyDialog.Handle ist ein C# Feature names Discard: docs.microsoft.com/en-us/dotnet/csharp/discards

    Das Invoke im zweiten Codefragment braucht man, damit es keine InvalidOperationException wegen Thread-Übergreifendem Zugriff auf den Dialog bekommt. Wie gesagt, du kannst die Zeile einfach mal durch busyDialog.Close(); ersetzen, dann siehst du, was ich meine. Ist auch gut zu Lernzwecken!

    TRiViUM schrieb:

    Das heißt, ich müsste für jede Arbeitsroutine, welche ich mit dem IsBusy-Dialog abarbeiten möchte, nur noch in eine weiteren Funktion/Methode kapseln, die ich mit dem Schlüsselword async ausschmücke?


    Jein, hier musst du aufpassen. Wenn du zu einer Methode nur async im Header hinzufügst, wird sie dennoch synchron ausgeführt. Visual Studio warnt dich hier sogar. Probier es mal aus und füge folgenden Code ein:

    C#-Quellcode

    1. public async void DoSth()
    2. // ^ Hier sollte eine Warnung auftauchen
    3. {
    4. // ...
    5. }


    Was du machen musst, ist folgendes:

    Wenn du eine Methode hast, die schon async ist und auch innendrin await nutzt, dann kannst du den BusyDialog so nutzen:

    C#-Quellcode

    1. await BusyDialog.ShowAsync(MyAsyncMethod);
    2. async Task MyAsyncMethod()
    3. {
    4. await Foo();
    5. }


    Wenn du eine Methode hast, die asynchron ausgeführt werden soll, aber noch nicht async nutzt, musst du sie irgendwie async machen. Die leichteste Methode ist über Task.Run(...):

    C#-Quellcode

    1. await BusyDialog.ShowAsync(Task.Run(() => MySynchronousMethod()));
    2. void MySynchronousMethod()
    3. {
    4. Foo();
    5. }


    Task.Run() sorgt dafür, dass die angegebene Methode auf den Threadpool ausgelagert wird. In den meisten Fällen heißt das, dass sie auf einem anderen Thread läuft und somit nicht mehr dein GUI blockiert.

    :!: Bitte zeig mir mal, wie deine Methode genau aussieht - dann kann ich dir sagen, wie du sie in den BusyDialog integrierst.

    TRiViUM schrieb:

    Vielleicht könnte man ne Erweiterungsmethode schreiben, damit man das Form direkt über busyDialogInstance.ShowDialogAsync() aufrufen könnte...


    Das kann man, aber das würde ich nicht empfehlen. Der Grund ist, dass du durch eine Erweiterungsmethode wieder eine zusätzliche Instanz erstellen musst und somit längeren Code ohne Mehrgewinn bekommst.
    Zum Vergleich, hier beide Möglichkeiten:

    C#-Quellcode

    1. // 1:
    2. await BusyDialog.ShowAsync(() => ...);
    3. // 2:
    4. var dialog = new BusyDialog();
    5. await dialog.ShowAsync(() => ...);


    #2 ist länger, ohne einen Vorteil zu bekommen. Das Ziel sollte immer sein, so wenig Code wie möglich/so verständlichen Code wie möglich zu schreiben. #1 ohne Extension wäre somit besser. Solltest du noch weitere Features im BusyDialog brauchen, sag mal Bescheid. Ich bin mir sicher, dass man die elegant integriert bekommt. :)


    Ich hoffe, dass das ganze klar genug ist. Das ganze ist teils ein komplexes Thema und ich kenne deinen Wissensstand nicht - wenn etwas zu unverständlich ist, sag Bescheid. Fragen kostet nichts. :)
    Super-geile Lösung!! :thumbsup:

    Ich würd höchstens noch die beiden static methods in das BusyDialogForm hineinverlegen, statt da eine Extra-Klasse für anzulegen (die dann ja doch vom BusyDialogForm abhängig ist).

    Ähm - du hast nicht zufällig Lust, einen Tipp daraus zu machen, für die TippsnTricks?

    Weil das ist ja wirklich eine allgemeingültige Lösung eines Standard-Problems - die viele brauchen können.

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

    Freut mich zu hören! :)

    ErfinderDesRades schrieb:

    Ich würd höchstens noch die beiden static methods in das BusyDialogForm hineinverlegen, statt da eine Extra-Klasse für anzulegen (die dann ja doch vom BusyDialogForm abhängig ist).


    Das geht natürlich auch. Wenn man die Trennung nicht vollzieht würde ich die Methoden auch nicht mehr statisch machen, sondern direkt als Instanz-Methoden im BusyDialogForm zur Verfügung stellen. Dann ist man in der Zukunft flexibel und kann beispielsweise ohne Umwege mit Cancel-Buttons/Events hantieren.
    Je nach Sichtweise ist es vllt sogar noch einen Schritt sauberer, weil dann der User entscheidet, wo und wann die Instanz der Form erstellt wird.

    ErfinderDesRades schrieb:

    Ähm - du hast nicht zufällig Lust, einen Tipp daraus zu machen, für die TippsnTricks?
    Weil das ist ja wirklich eine allgemeingültige Lösung eines Standard-Problems - die viele brauchen können.


    Dafür fehlt mir momentan leider die Zeit. Hab streng genommen schon mit dem Erstellen des Projektes zu viel prokrastiniert. Aber wenn du oder jemand anders das übernehmen möchte, feel free! Den Code/das Projekt könnt ihr ja einfach übernehmen oder auch nach Bedarf anpassen.

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

    @shad
    Jo, dass der Compiler einem nicht mehr helfen kann, wenn man dynamic verwendet, ist verständlich.
    Ich bin auch ein großer Fan davon, wenn alles schön typisiert ist, deshalb auch von mir noch großes Danke, dass Du dir die Zeit genommen und es verbessert hast! :thumbsup:

    Gut zu wissen, dass es dieses Feature discard gibt. So fällt endlich das Anlegen von Dummy-Variablen weg, das kannte ich nämlich noch nicht...

    Jetzt wird mir auch erst klar, warum man an der Stelle invoken muss - sind ja 2 unterschiedliche Threads :D

    shad schrieb:

    Wenn du zu einer Methode nur async im Header hinzufügst, wird sie dennoch synchron ausgeführt
    Okay, auch das habe ich nun verstanden. Wenn async in der Deklaration steht, muss es auch mit await intern arbeiten um asynchron ausgeführt werden zu können :)

    shad schrieb:

    Bitte zeig mir mal, wie deine Methode genau aussieht - dann kann ich dir sagen, wie du sie in den BusyDialog integrierst

    Im Prinzip arbeitet keine meiner Methoden mit async/await, weshalb ich mich am Anfang vielleicht etwas schwer getan habe den Vorgang, was da passiert, nachzuvollziehen :whistling:

    shad schrieb:

    Task.Run() sorgt dafür, dass die angegebene Methode auf den Threadpool ausgelagert wird

    Okay, man findet teilweise gefährliches Halbwissen auf anderen Internetseiten, aber das Prinzip habe ich jetzt denke ich verstanden 8-)

    Die Methoden/Funktionen, um die es bei geht, sind zum Auslesen von ner Steuerung über nen COM-Port.
    Sollen nach einer Anfrage Daten von der Steuerung empfangen werden, soll der Benutzer nix machen können:

    C#-Quellcode

    1. /// <summary>
    2. /// Reads a data paket from controller.
    3. /// </summary>
    4. /// <param name="response">The reference where the data paket will be allocated to.</param>
    5. /// <param name="timeOut">The Timeout, in which time the controller have to respond.</param>
    6. /// <returns>If data paket was complete/valid, it will return True, otherwise False.</returns>
    7. private bool Read( ref List<byte> response, int timeOut = READ_TIMEOUT )
    8. {
    9. bool result = false;
    10. // declare dynamic response buffer
    11. List<byte> payload = new List<byte>(); //userdata
    12. List<byte> buffer = new List<byte>(); //complete receive buffer
    13. lock( this )
    14. {
    15. try
    16. {
    17. bool isEscapedByte = false; // marker for escaped data byte
    18. bool dataComplete = false; // marker for transmission complete
    19. // set timeout for first byte
    20. com.ReadTimeout = timeOut;
    21. while( !dataComplete )
    22. {
    23. // read data byte
    24. byte dataByte = (byte)com.ReadByte();
    25. buffer.Add( dataByte ); // only debugging
    26. // set read timeout after receiving first byte
    27. if ( com.ReadTimeout != READ_TIMEOUT )
    28. com.ReadTimeout = READ_TIMEOUT;
    29. // data check
    30. if( dataByte == STX) // 0x02
    31. {
    32. // start of transmission
    33. payload.Clear();
    34. buffer.Clear();
    35. buffer.Add( dataByte );
    36. }
    37. else if( dataByte == ETX ) // 0x03
    38. {
    39. //end of transmission
    40. dataComplete = true;
    41. // exit while loop
    42. break;
    43. }
    44. else
    45. {
    46. // data handling
    47. if( isEscapedByte )
    48. {
    49. // reset marker
    50. isEscapedByte = false;
    51. //handle escaped byte
    52. payload.Add( (byte)(dataByte - 0x10) ); // remove dec 16
    53. }
    54. else if( dataByte == SOH ) // 0x01
    55. {
    56. // set marker for next escaped byte
    57. isEscapedByte = true;
    58. }
    59. else if( dataByte > 0x03)
    60. {
    61. // add data byte to buffer
    62. payload.Add( dataByte );
    63. }
    64. }
    65. }
    66. }
    67. catch( TimeoutException)
    68. {
    69. log( Logger.MessageType.WARNING, "Timeout while reading." );
    70. }
    71. catch( Exception ex)
    72. {
    73. log( Logger.MessageType.ERROR, "read error: " + ex.Message );
    74. //timeout
    75. //buffer = null;
    76. }
    77. }
    78. // allocate response buffer
    79. response = payload;
    80. log( Logger.MessageType.INFO, "Received: " + GlobalEnvironment.ByteArrayToHexString( buffer ) );
    81. // set result
    82. result = buffer.Count >= 3 && buffer[0] == STX && buffer[buffer.Count-1] == ETX; // 3 bytes are minimal response
    83. //// check command byte
    84. //if( buffer[1] == CMD_UNKNOWN )
    85. // log( Logger.MessageType.WARNING, "Unknown command for controller" );
    86. if ( !result )
    87. {
    88. log( Logger.MessageType.WARNING, "Framing error while reading" );
    89. MessageBox.Show( "Communication error.", "Controller read", MessageBoxButtons.OK, MessageBoxIcon.Error );
    90. }
    91. // return result
    92. return result;
    93. }


    Diese Funktion wird von vielen anderen Funktionen verwendet, da sie zum Lesen benutzt wird.
    Dort macht ein ProcessReport zwar kein Sinn, aber in einer übergeordneten Funktion schon - die weiß zb. wie oft ich was bestimmtes Auslesen muss.

    Da würde man dann wieder zum Post von @ErfinderDesRades kommen, wo das mit dem Thema ReportProgesss und Cancelation behandelt wird.

    Komme erst ende nächster Woche dazu, das alles mal zu studieren und da weiter zu machen. Aber mit dieser Grundlage sollte man doch nun bestens bedient sein :saint:

    shad schrieb:

    Code ohne Mehrgewinn
    Jo, sehe ich ein. Und da Programmierer von Natur aus faul sind, geht der Punkt an...shad, shame on me :D

    shad schrieb:

    Ich hoffe, dass das ganze klar genug ist. Das ganze ist teils ein komplexes Thema und ich kenne deinen Wissensstand nicht
    Wie gesagt, bin mit dem Thema noch nicht so vertraut, allerdings bin ich dir für die Erklärungen dankbar. Und ich bin auch ein großer Fan von LearningByDoing, Werde mir die Solution auch herunterladen und mal etwas mit spielen :saint:

    shad schrieb:

    Fragen kostet nichts
    Das stimmt, keine Angst, die werden noch kommen :rolleyes:

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

    zu deiner read-Methode:
    • KlassenMember würde ich Gross benamen.
    • Der Parameter response sollte nicht byref sein.
    • Ein Doku-Kommentar wäre nett, der erklärt, was das Teil machen soll, und was der return-Wert bedeuten soll.
    • Und wieder scheint mir die komplette bestehende Kommentation üflüssig (während das wesentliche an Kommentation fehlt).
      Ich glaub, wenn ich das nur schematisch überarbeiten würde (ohne jede inhaltliche Änderung), dann käme die Methode mit 40 Zeilen aus. Was ein enormer Vorteil für die Leserlichkeit wäre, weil dann passt sie auf eine Bildschirmseite.

    ErfinderDesRades schrieb:

    KlassenMember würde ich Gross benamen.
    Hatte ich da wohl nur noch nicht abgeändert, VS schlägt es einem ja auch vor, jetzt wo Du es sagst/schreibst.


    ErfinderDesRades schrieb:

    Der Parameter response sollte nicht byref sein.
    Okay, kannst Du das genauer begründen?


    ErfinderDesRades schrieb:

    Ein Doku-Kommentar wäre nett, der erklärt, was das Teil machen soll, und was der return-Wert bedeuten soll.
    Ich war/bin mir noch nicht sicher, ob die Routine so bleiben wird und wollte die Doku zum Schluss machen, wenn alles final ist.
    Aber für Euch hier wäre es tatsächlich hilfreicher, zugegeben :whistling:
    Zur Vollständigkeit hab ich es im entsprechenden Post editiert.


    ErfinderDesRades schrieb:

    die komplette bestehende Kommentation üflüssig
    Für uns vermutlich schon und für mich sowoieso. Die Kommentare hab ich nicht extra hier für geschrieben, sondern für nen Bekannten, der das Protokoll auch implementieren wollte (wir hatten bereits über das Protokoll geplaudert).

    TRiViUM schrieb:

    Die Kommentare hab ich nicht extra hier für geschrieben, sondern für nen Bekannten, der das Protokoll auch implementieren wollte (wir hatten bereits über das Protokoll geplaudert).
    Da muss man aber schon seehr bekannt sein, wenn man solche Kommentation nötig hat:

    C#-Quellcode

    1. // set result
    2. result = [...]
    3. [...}
    4. // return result
    5. return result;




    TRiViUM schrieb:

    ErfinderDesRades schrieb:
    Der Parameter response sollte nicht byref sein.
    Okay, kannst Du das genauer begründen?
    Muss ich nicht - im Gegenteil: Du musst begründen, warum der Parameter byref sein soll.
    Na? - kannste nicht?
    siehste - also weg damit!
    (ich kann dir auch sagen, warum du's nicht kannst: Es gibt einfach keinen Grund dafür, diesen Parameter byref zu machen.)



    [Doku-Kommentar}
    thx - die Erklärung für "response" ist (mir) zwar unverständlich, und "timeOut" bedarf imo keiner Erklärung, aber trotzdem kann ich mir den Sinn nun so ungefähr zusammenreimen.

    ErfinderDesRades schrieb:

    wenn man solche Kommentation nötig hat
    Nötig sind diese ganz und gar nicht, eher einfach nur zur Konsistenz, auch wenn unnötig.


    ErfinderDesRades schrieb:

    Du musst begründen, warum der Parameter byref sein soll
    Da die Funktion von vielen anderen Methoden/Funktionen verwendet wird, habe ich so die Möglichkeit, zum einen über den Return-Wert auf ein gültiges Paket zu prüfen und anschließend über die Referenz mit dem gültigen Datenpaket weiter arbeiten zu können.
    Bei einem ungültigen Datenpaket (Result-Wert FALSE) haben gewisse Routinen (noch nicht) die Möglichkeit, aus den vorhandenen Daten (byRef) teilweise die fehlenden zu vervollständigen. Ich brauche also 2 Rückgabewerte und die wollte ich nicht in eine separate Struktur/Klasse verfrachten welche dann als Result-Wert zurückgegeben wird.

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

    TRiViUM schrieb:

    Da die Funktion von vielen anderen Methoden/Funktionen verwendet wird, habe ich so Möglichkeit, zum einen über den Return-Wert auf ein gültiges Paket zu prüfen und anschließend über die Referenz mit dem gültigen Datenpaket weiter arbeiten zu können.
    Bei einem ungültigen Datenpaket (Result-Wert FALSE) haben gewisse Routinen (noch nicht) die Möglichkeit, aus den vorhandenen Daten (byRef) teilweise die fehlenden zu vervollständigen. Ich brauche also 2 Rückgabewerte und die wollte ich nicht in eine separate Struktur/Klasse verfrachten welche dann als Result-Wert zurückgegeben wird.
    Versteh ich nicht.
    Imo könnteste eine payLoad-List (byval) reinreichen, und die Methode würde die befüllen. je nachdem, ob die Befüllung geklappt hat, returnt die Methode true/false.

    "über Referenz mit gültigem Datenbpaket weiterarbeiten" versteh ich schon semantisch nicht (also was bedeutet dieser Satz?).
    Und dass du 2 Rückgabewerte brauchst sehe ich auch nicht. Wie gesagt: Payload befüllen und returnen, obs geklappt hat sollte imo ausreichen.

    ErfinderDesRades schrieb:

    Imo könnteste eine payLoad-List (byval) reinreichen, und die Methode würde die befüllen. je nachdem, ob die Befüllung geklappt hat, returnt die Methode true/false.
    verstehe ich wiederum nicht.
    byval ist doch keine Art Verweis ?(

    Oder wir reden aneinander vorbei, die Liste Payload wird ja erst in der Read-Funktion befüllt, und auch nur da.
    Der Aurfufer will dann erstens wissen, ob ein gültiges Paket empfangen wurde (Return-Wert) und falls ja, muss er an die Payload-Liste kommen, um diese auswerten zu können.
    Hi, ich mische mich hier auch nochmal ein.

    @TRiViUM An dieser Stelle möchte ich kurz sagen, dass ich es echt schön finde, wie lernwillig du bist. Du gehst auf jede Antwort und und hinterfrägst die Dinge, die du nicht verstehst. Das empfinde ich als sehr gute Eigenschaft, von daher, weiter so! :thumbsup:

    TRiViUM schrieb:

    verstehe ich wiederum nicht.
    byval ist doch keine Art Verweis

    Verstehe ich es richtig, dass du meinst, dass du die Liste nicht verändern kannst, wenn du sie ohne ref übergibst? Wenn ja: Das stimmt so nicht - lass mal dieses Beispiel hier laufen: dotnetfiddle.net/YgJv0h
    Das ist ein wichtiger Punkt in .NET und im Endeffekt der Unterschied zwischen Strukturen struct und Klassen class. Klassen sind sogenannte Referenztypen, sprich, wenn du sie an eine Methode übergibst (ohne out, ref oder sonstigem) arbeitest du immer mit der selben Instanz! Das kannst du im verlinkten Beispiel sehen. Die Methode fügt der übergebenen Liste Elemente hinzu. Diese sind auch nach dem Aufruf in der main() Methode präsent.

    ref ist eigentlich nur dafür gedacht, innerhalb einer Methode eine Variable von außerhalb zu ändern. Dieses Feature sollte generell mit Vorsicht genutzt werden, da es den Code schwerer zu verstehen macht. Im Prinzip hat ein Programmierer, der eine Methode mit ref nutzt, auf mehr zu achten: Die Methode gibt nicht nur, wie sonst, einen Wert zurück, sondern sie kann auch zusätzlich noch State an anderen Stellen modifizieren. Das kann, wenn man nicht gut aufpasst, zu Bugs oder zumindest zu Verwirrung führen, die schwer zu finden sind.
    Auf die Schnelle habe ich den Kommentar hier zum Thema gefunden: reddit.com/r/learnprogramming/…l_over_the_place/coum2hn/

    Auf ref trifft eigentlich genau das gleiche zu, wie für dynamic: Nutze es nur, wenn du es absolut brauchst und es keine andere Möglichkeit gibt, dein Ziel zu erreichen.

    Jetzt die Frage, wie kannst du sonst dein Ziel erreichen, die Liste zu befüllen und auch zurückzugeben, ob es geklappt hat? Hier gibt es viele Möglichkeiten:
    1. Du kannst den bool result komplett entfernen - eigentlich brauchst du ihn nicht. Das geht, indem du...:
      1. ...eine befüllte List<...> zurückgibst, wenn alles geklappt hat und...
      2. ...null zurückgibst, wenn etwas nicht geklappt hat. Der Aufrufer kann dann prüfen, ob das Ergebnis null ist. Wenn nein, hat es geklappt. Wenn ja, gab es einen Fehler.
    2. Du kannst ein Tupel (bool Result, List<...> Items) zurückgeben. Das ist im Endeffekt die gleiche Art, wie du sie gerade hast, sprich, zwei Ergebnisse auf einmal, aber explizit und verständlich.
    3. Du kannst eine Exception werfen und fangen. Das ist situationsbedingt, aber oft eine gute Alternative, speziell, wenn der Fehler nicht leicht umgangen werden kann (z.B. durch neu versuchen).
    4. Vielleicht (!) kannst du auch bei Erfolg eine befüllte und bei Fehlern eine leere Liste zurückgeben. Das hängt allerdings davon ab, ob eine leere Liste theoretisch auch ein valides Ergebnis sein kann.
    Generell bin ich meistens für Exceptions, da du hier genau weißt, welcher Fehler genau aufgetreten ist. War es ein Timeout? War es ein Fehler durch inkonsistente Daten? Hat eine Datei gefehlt? Ist mir der RAM ausgegangen? Mit einem result, egal ob bool oder null weiß man nur, dass ein Fehler aufgetreten ist, aber nicht mehr welcher. Hängt aber natürlich auch davon ab, wo du gerade bist. Nicht immer brauchst du diese granularen Informationen. Diese Überlegungen sind zugegebenermaßen eher dann wichtig, wenn du beispielsweise eine Library schreibst. Für echte Programme ist es meist nur wichtig, dass ein Fehler nicht zum Absturz führt.

    Auch wenn das eigentlich nicht Thema des Threads sein sollte: Wenn ich schon dabei bin kommentiere ich auch die Thematik der Kommentare - prinzipiell hat EDR hier schon Recht. Kommentare wie der Folgende verlängern nur unnötig den Code:

    C#-Quellcode

    1. // return result
    2. return result;

    Es mag fies sein, so darauf rumzuhacken, aber warum wurde hier kommentiert? Der Code beschreibt ja genau das gleiche wie der Kommentar, nämlich, dass result zurückgegeben wird.

    Ein Merksatz, der mir damals sehr viel geholfen hat, ist: "Kommentiere nie, was der Code macht. Kommentiere, warum er etwas macht."
    Der Grund dafür ist einfach, aber wichtig: Kein Kommentar kann jemals so gut erklären, was Code tut, wie der eigentliche Code selbst. Im Code steht nämlich ganz genau, was passiert. Viel wichtiger ist für das Verständnis der anderen allerdings, warum etwas passiert, also in welchem Kontext Code ausgeführt wird.

    Als einfaches Beispiel, schau dir mal diese Methode an:

    C#-Quellcode

    1. void Example(List<int> lst)
    2. {
    3. lst.AddRange(new[] { 1, 2, 3, 4, 5, 6 });
    4. }


    Sehr einfach, aber absolut unverständlich. Wieso werden hier diese Zahlen hinzugefügt? Kommentare können das erklären und sind dafür auch sinnvoll:

    C#-Quellcode

    1. void Example(List<int> lst)
    2. {
    3. // Simuliert die heutige Lotto-Ziehung.
    4. // Die Zahlen sind immer die selben, damit man das Ergebnis leicht testen kann.
    5. lst.AddRange(new[] { 1, 2, 3, 4, 5, 6 });
    6. }


    Der Vollständigkeit halber muss ich an dieser Stelle noch sagen, dass ein Kommentar hier immer noch überflüssig ist - der Code lässt sich noch mehr verbessern, indem man der Methode und Liste bessere Namen gibt. Das nennt sich dann quasi "Documentation through Code" - ich brauche keine Kommentare, wenn der Code bereits dokumentiert, was Sache ist:

    C#-Quellcode

    1. void FülleListeMitSimulierterLottoZiehung(List<int> lottoZahlen)
    2. {
    3. lst.AddRange(new[] { 1, 2, 3, 4, 5, 6 });
    4. }


    Das Ganze könnte man jetzt zum Beispiel gut auf deine Methode anwenden. Ich werde es nicht komplett durchgehen, aber einige Beispiele hier:
    • Momentan heißt sie Read(...). Was genau ließt sie denn? Wenn es Steuerdaten sind, kannst du durch ein einfaches Renaming schon viel Kontext hinzufügen: ReadControlDataFromSerialPort(...).
    • Deklarationen/Kommentare wie List<byte> buffer = new List<byte>(); //complete receive buffer kann man auch umgehen. Wenn buffer die empfangenen Daten speichert, dann benenne ihn doch so: List<byte> receivedDataBuffer. Auf einmal ist der Kommentar nicht mehr nötig und an jeder Stelle, die in Zukunft im Code erscheint, weiß man genau, was genau eigentlich in der Liste enthalten ist.
    • Gut, sowas wie // exit while loop oder das return result von oben hatten wir ja schon. Wenn andere Leute nicht wissen, was ein break in C# macht, dürfen sie sich hier nicht auf Kommentare verlassen, sondern sollen Google benutzen.



    Edit: Noch ein kleiner Nachsatz, der vielleicht auch hilfreich ist:

    ErfinderDesRades schrieb:

    "timeOut" bedarf imo keiner Erklärung

    timeout bedarf in diesem Beispiel absolut einer Erklärung! Nämlich, da es nur ein int ist, welche Einheit er genau ist. Sekunden? Millisekunden? Das ist tatsächlich ein weiterer Punkt, an dem Kommentare nötig sind - um Sachen zu erklären, die per Code schwer ausgedrückt werden können.

    Noch besser wäre, wenn der Parameter z.B. timeoutInMsecs heißt, oder, am allerbesten, kein int ist, sondern ein TimeSpan (welches alles sein kann, z.B. TimeSpan.FromSeconds(3)).

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

    shad schrieb:

    du meinst, dass du die Liste nicht verändern kannst, wenn du sie ohne ref übergibst? Wenn ja: Das stimmt so nicht

    Ja, genau das dachte ich.
    Das erklärt auch, was EDR damit meinte, jetzt wirds verständlich...mein Fehler :whistling:
    Dann nehme ich alles zurück und behaupte das Gegenteil :D

    Und wenn man das Code-Beispiel laufen lässt, sieht man das von Dir genannte Ergebnis, war ich mir nicht drüber bewusst.
    Zu meiner eigenen Beruhigung kann ich sagen, dass ich das ref nur in diesem Projekt verwendet habe bzw. vorher noch nie mehrere Sachen in einer Funktion zurückgeben musste.
    Ist demnach ja eine wichtige grundlegende Angelegenheit, über die ich bislang noch nicht gestolpert bin.

    shad schrieb:

    den Kommentar hier zum Thema gefunden
    Das sehe ich nun auch voll und ganz ein, weshalb refnicht verwendet werden sollte/muss, werde es auch abändern.
    Genau so wie das mit dem dynamic, wie du schon gesagt hast, denn auch da gehe ich davon aus, dass ich es nie benötigen werde, jedenfalls definitiv nicht dort.

    shad schrieb:

    Jetzt die Frage, wie kannst du sonst dein Ziel erreichen
    Jo, jetzt macht auch das von EDR für mich wieder Sinn, habt ja beide den gleichen Ansatz gehabt.

    Auch korrupte Daten können/sollen hier verarbeitet werden. Momentan prüft Read(), indem auf das erste und letzte Byte geschaut wird und setzt darauf hin den Return-Wert.
    Damit wollte ich nur umgehen, dass ich in jeder anderen Routine, welche Read() benutzt, prüfen muss, ob die Daten gültig sind (Anfang und Ende mit konstantem Wert).
    Aber da ich ja eh schon in jeder aufrufenden Routine prüfen muss, ob Read() erfolgreich war, kann ich hier auch gleich die Prüfung der gültigen Daten (Anfang und Ende mit konstantem Wert) machen.
    Also ja, es gibt tatsächlich hier keinen Grund, ref zu benutzen und das Result kann ich vermutlich auch von Bool in List<..> ändern.


    shad schrieb:

    die Thematik der Kommentare
    Ich bin da ja voll und ganz bei euch, und Deinen Merksatz schreibe ich mir auch hinter die Ohren.
    Bei den Namen der Routinen war ich wohl nicht sonderlich kreativ/hilfreich, allerdings steckt Read() in der Klasse Controller.cs und liest alle Daten aus, die sich zu dem Zeitpunkt im Eingangspuffer befinden bzw. bis ein bestimmtes Byte kommt. Da ich sie über Controller.Read() aufrufe, hab ich mir nur gespart, ReadReceiveBuffer draus zu machen.


    shad schrieb:

    warum wurde hier kommentiert?
    Diese Kommentare habe ich nicht geschrieben, als ich damit fertig war, sondern wenn ich solche Routinen programmiere, schreibe ich mir zuerst in den kommentanren rein, was ich alles machen muss bzw. wie ich mir das vorstelle, um auch am nächsten Tag noch zu wissen, was ich vorhatte. Diese Kommentare hatte ich dann nur nicht gelöscht, nachdem ich gewisse Sachen abgearbeitet habe.
    Ich glaube auch, dass ich die definitiv noch überarbeiten sollte und erst recht, bevor ich es mit dem besagten Bekannten teile.


    Und noch was zum ersten Post von mir zu dem dynamic:
    Ich habe scheinbar die Beschreibung in den Überladungen verwechselt und dachte, dass der erste Parameter der Input ist, und nicht das Output.
    Hab nämlich gerade selber noch mal durch die Solutions geschaut.

    Achja:

    shad schrieb:

    hinterfrägst die Dinge, die du nicht verstehst
    wenn ich etwas besser machen kann, bin ich grundsätzlich auch daran interessiert, wie ^^

    shad schrieb:

    Solltest du noch weitere Features im BusyDialog brauchen, sag mal Bescheid
    Bescheid :D
    Ich habe jetzt versucht, ein Fortschritt mit zu integrieren, leider nicht ganz funktional...

    Dazu habe ich im Aufrufer des Busy-Dialogs ein Event angeleg:

    C#-Quellcode

    1. public Progress<int> ProgressPercent = new Progress<int>();


    Dieses Event abboniert der Busy-Dialog nach InitializeComponents.

    Ausgelöst wird das Event dann in der lange dauernden Action:

    C#-Quellcode

    1. var prgs = ge.ProgressPercent as IProgress<int>;
    2. prgs.Report( (int)progress );


    Und im Busy-Dialog wird dann darauf reagiert:

    C#-Quellcode

    1. private void ProgressPercent_ProgressChanged( object sender, int e )
    2. {
    3. pbProgress.BeginInvoke( (Action)( ()
    4. =>
    5. {
    6. pbProgress.Value = e;
    7. } ) );
    8. }


    Das funktioniert allerdings nur beim aller ersten Aufruf von dem Busy-Dialog.Beim zweiten mal aufrufen bekomme ich eine Exception:

    Quellcode

    1. Ein Ausnahmefehler des Typs "System.InvalidOperationException" ist in System.Windows.Forms.dll aufgetreten.
    2. Zusätzliche Informationen: Invoke oder BeginInvoke kann für ein Steuerelement erst aufgerufen werden, wenn das Fensterhandle erstellt wurde.


    Kann man mir erklären, warum? ?(
    Zudem es ja beim ersten mal funktioniert...

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