Parallelisierung einer Methode

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

Es gibt 11 Antworten in diesem Thema. Der letzte Beitrag () ist von Pinki.

    Parallelisierung einer Methode

    Hallo,
    ich habe eine Klasse Process mit der Methode Work(). Von dieser Klasse habe ich nun x Instanzen in einer List<Process> und diese sollen nacheinander asynchron Work() abarbeiten. Es soll also erst Process[0].Work() ablaufen, dann Process[1].Work() etc. Es läuft also immer nur 1 Thread (naja einer für Process und einer für GUI). Zusätzlich muss ich den laufenden Thread abbrechen können und den Progress reporten. Da ich mit TAP noch nicht so klar komme, hatte ich gedacht ich frag mal nach ^^
    Habe mal folgendes Testprojekt gemacht:

    Spoiler anzeigen

    C#-Quellcode

    1. public partial class Form1 : Form
    2. {
    3. Task myTask;
    4. Process myTest = new Process();
    5. private void btnStart_Click(object sender, EventArgs e)
    6. {
    7. myTask = new Task(() => myTest.Work(new Progress<float>(ReportProgress)), TaskCreationOptions.LongRunning);
    8. myTask.Start();
    9. }
    10. private void ReportProgress(float progress)
    11. {
    12. BeginInvoke((Action)(() => label1.Text = progress.ToString()));
    13. }
    14. }
    15. public class Process
    16. {
    17. public void Work(IProgress<float> progressReport) // Mache lange andauerndes Zeugs (später so ca ein paar Stunden)
    18. {
    19. for (int i = 0; i <= 10; ++i)
    20. {
    21. Thread.Sleep(2000);
    22. progressReport.Report(i / 10.0f);
    23. }
    24. }
    25. }

    ​Mir sieht das nicht ganz richtig aus. Wie müsste ich async/await einsetzen? Brauche ich die vielleicht gar nicht? Dann hat die Process Klasse auch diverse Properties die alle im UI-Thread erstellt wurden. Auf diese wird nicht während des Ablaufs von Work() zugegriffen, sollte ich trotzdem besser ein Lock oder Ähnliches benutzen? Wie würde das Abbrechen des Tasks funktionieren, kann ich irgendwie ne CancellationFlag übergeben?

    Grüße
    Hi,

    markiere den Handler als async void und dann kannst Du darin z. B. immer await Task.Run(() => ...); machen. Kannst auch mal Task.WhenAll anschauen. Das ist auch ein awaitable Task. Allerdings meine ich, dass dort nicht nacheinander abgearbeitet wird, sondern halt parallel. Ist also wohl nicht, was Du suchst.
    Abbrechen geht mit CancellationTokens. Die übergibst Du und musst halt in Deiner Methode immer deren Status prüfen. Dann wirft man in der Regel eine OperationCanceledException.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Mit Async/Await ists immer dasselbe Rezept: Schreib deine Methode so, dass sie ihren Job macht - zunächstmal ohne Nebenläufigkeit, und dann blockiert sie eben das Gui.
    Und lass ProgressReport erstmal weg.
    Diese Methode kann man dann mit paar Handgriffen umstellen - in der einfachsten Version ist nicht eine einzige zusätzliche Zeile Code vonnöten.
    Und dann kann man ProgressReport einbauen, und auch die weiteren wünschenswerten Dinge.

    mach das erstmal und poste, dann frickel ich das um.
    Oder versuche zu verstehen: codeproject.com/Articles/10296…ithout-any-additional-Lin

    ErfinderDesRades schrieb:

    Oder versuche zu verstehen

    Das ist das Ziel ;) Ich les es mir gleich mal durch.
    Die Methode ist soweit ja schon fertig. Das Projekt ist aber relativ groß, deswegen wollte ich es erstmal in einem leeren Projekt versuchen. Damit das Prinzip klar wird.
    / Edit
    Ich hab da direkt mal ne Frage zu dem Artikel. Wieso muss ich bei meinem ProgressReport invoken und du nicht? Der einzige Unterschied ist, dass ich Progress<T> als Parameter übergeben und du greifst direkt auf die Instanz zu. Kriegt dein Progress<T> noch iwie den SyncContext oder warum kannst du so auf die Steuerelemente zugreifen?

    @Trade
    Das mit dem Abbrechen klappt soweit.

    Spoiler anzeigen

    C#-Quellcode

    1. public partial class Form1 : Form
    2. {
    3. Process myTest = new Process();
    4. CancellationTokenSource ct = new CancellationTokenSource();
    5. private async void btnStart_Click(object sender, EventArgs e)
    6. {
    7. CancellationToken token = ct.Token;
    8. try
    9. {
    10. await Task.Run(async () =>
    11. {
    12. await myTest.Work(new Progress<float>(ReportProgress), token);
    13. }
    14. , ct.Token);
    15. }
    16. catch(OperationCanceledException)
    17. {
    18. // Abbruch
    19. }
    20. }
    21. private void ReportProgress(float progress)
    22. {
    23. BeginInvoke((Action)( () => label1.Text = progress.ToString()));
    24. }
    25. private void btnCancel_Click(object sender, EventArgs e)
    26. {
    27. ct.Cancel();
    28. }
    29. }
    30. public class Process
    31. {
    32. public async Task Work(IProgress<float> progressReport, CancellationToken token)
    33. {
    34. for (int i = 0; i <= 10; ++i)
    35. {
    36. await Task.Delay(1000);
    37. token.ThrowIfCancellationRequested();
    38. progressReport.Report(i / 10.0f);
    39. }
    40. }
    41. }

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

    Gonger96 schrieb:

    Wieso muss ich bei meinem ProgressReport invoken und du nicht?
    Vermutlich, weil Dein Progress nicht im Hauptthread deklariert wird, sondern erst im Task.Run. Würde ich zumindest jetzt auf den ersten Blick so sehen. Deklariere das mal außerhalb und übergib das dann erst als Parameter. Normal braucht man nämlich auch kein Invoke.

    Das mit der Cancellation sollte passen. :) Allerdings musst Du das Work nicht nochmal explizit awaiten. Also den async delegate kannst Du zu einem normalen umformen, würde ich sagen. Die Methode wird eh asynchron laufen, sodass da async-await eigentlich auch überflüssig wäre. Aber gut, für das ​Task.Delay brauchst es natürlich. ^^

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:

    Trade schrieb:

    Deklariere das mal außerhalb

    ​Bringt keine Änderung, bekomme direkt ne InvalidOperation. Ist wahrscheinlich irgendwas dummes, aber ich sehe da erstmal keinen Unterschied zu dem Beispiel auf CodeProject.

    C#-Quellcode

    1. ​ Progress<float> reportProgress = new Progress<float>();
    2. public Form1()
    3. {
    4. InitializeComponent();
    5. reportProgress.ProgressChanged += ReportProgress_ProgressChanged;
    6. }
    7. private void ReportProgress_ProgressChanged(object sender, float e)
    8. {
    9. label1.Text = e.ToString();
    10. }
    11. private async void btnStart_Click(object sender, EventArgs e)
    12. {
    13. await Task.Run(
    14. () =>
    15. {
    16. Test();
    17. }
    18. );
    19. }
    20. private void Test()
    21. {
    22. IProgress<float> prg = reportProgress;
    23. for (int i = 0; i <= 10; ++i)
    24. {
    25. Thread.Sleep(1000);
    26. prg.Report(i / 10.0f);
    27. }
    28. }
    Passiert das auch, wenn Du direkt progressReport ansprichst? Also nicht erst eine lokale Variable erstellen.
    Das mit dem Parameter übergeben ist btw schon richtig. Also muss/sollte nicht global sein.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Deklaration zu ​IProgress<float> reportProgress = new Progress<float>();.
    Wobei das irgendwie grade etwas wenig Sinn macht, dass das Interface die Methode hat und Progress<T> dann nicht. O.o
    Auf jeden Fall habe ich immer einen Parameter ​IProgress<T> und übergebe dem einen ​Microsoft.Progress<T>. Da geht das dann ganz normal.

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Frag mich nicht warum, aber so gehts:

    C#-Quellcode

    1. public partial class Form1 : Form {
    2. Progress<float> reportProgress;
    3. public Form1() {
    4. reportProgress = new Progress<float>();
    5. InitializeComponent();
    6. reportProgress.ProgressChanged += ReportProgress_ProgressChanged;
    7. btnStart.Click += btnStart_Click;
    8. }
    9. private void ReportProgress_ProgressChanged(object sender, float e) {
    10. label1.Text = e.ToString();
    11. }
    12. private async void btnStart_Click(object sender, EventArgs e) {
    13. await Task.Run(() => { Test(); });
    14. }
    15. private void Test() {
    16. IProgress<float> prg = reportProgress;
    17. for (int i = 0; i <= 10; ++i) {
    18. System.Threading.Thread.Sleep(1000);
    19. prg.Report(i / 10.0f);
    20. }
    21. }
    22. }
    Jo, so wie ich in Post 4. Ich hab mal in die Reference Source geguckt:

    Spoiler anzeigen

    C#-Quellcode

    1. /// <remarks>
    2. /// Any handler provided to the constructor or event handlers registered with
    3. /// the <see cref="ProgressChanged"/> event are invoked through a
    4. /// <see cref="System.Threading.SynchronizationContext"/> instance captured
    5. /// when the instance is constructed. If there is no current SynchronizationContext
    6. /// at the time of construction, the callbacks will be invoked on the ThreadPool.
    7. /// </remarks>

    So konnte ich den Fehler auch finden. Wenn ich den Progress<T> direkt global in der Klasse instanziiere hat er anscheinend noch nicht den CurrentSyncContext und nimmt sich dann den aus dem ThreadPool. Instanziiere ich im Konstruktor von Form1 läuft alles ohne Probleme. Was mich aber wundert ist, dass im CodeProject Artikel auch alles läuft :S . Könnte jemand mal testen ob SynchronizationContext context = SynchronizationContext.Current; == null ist, wenn es global in der Form deklariert wird? Bei mir ist das der Fall.

    ​/ Edit
    ​@'ErfinderDesRades'
    Du warst schneller :D ​ Liegt also iwie am SynchronizationContext.