none
TPL: Tasks beenden und anschließend neustarten

    Frage

  • Hallo zusammen,

    ich schreibe zurzeit an einer kleinen Anwendung, die mit Tasks arbeitet und soweit funktioniert auch (fast) alles.

    Ich meinen Task erzeugen starten und beenden. Mein Problem liegt aber beim Neustarten des Tasks, was anscheinend nicht funktioniert.

    Hier ist mein Test-Task-Prgramm:

     

    //###################
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Threading.Tasks;
    using System.Threading;

    namespace Test_TaskRestart
    {
        public delegate void UpdateGUI(String info);
        public partial class Form1 : Form
        {
            Task Testtask = null;

            CancellationTokenSource TaskToken = new CancellationTokenSource();

            public Form1()
            {
                InitializeComponent();
                button2.Enabled = false;
                this.CenterToScreen();
            }

            private void GUI_Info(String text)
            {
                label1.Text = text;
            }

            private void ToDos()
            {
                int i = 0;
                try
                {
                    while (true)
                    {
                        TaskToken.Token.ThrowIfCancellationRequested();
                        i++;
                        if (i % 25 == 0)
                        {
                            label1.Invoke(new UpdateGUI(this.GUI_Info), new Object[] { "Durchgang: " + i });
                        }
                    }
                } catch (Exception e) {
                    label1.Invoke(new UpdateGUI(this.GUI_Info), new Object[] { "ThrowIfCancellationRequested() ist ausgelöst worden" + e});
                }
            }

            private void button1_Click(object sender, EventArgs e)
            {
                //Start
                try
                {
                    button1.Enabled = false;

                    //Erzeugen und Starten des Tasks
                    Testtask = new Task(ToDos, TaskToken.Token);
                    Testtask.Start();    //HIER tritt der Fehler auf, obwohl ein neuer Task erzeugt wird.

                    button2.Enabled = true;
                }
                catch (Exception e1)
                {
                    try
                    {
                        Testtask.Dispose();
                    }
                    catch (Exception x)
                    {
                    }
                    label1.Text = "Fehler:" + e1.ToString();
                }
            }

            private void button2_Click(object sender, EventArgs e)
            {
                //Stop
                try
                {
                    TaskToken.Cancel();
                }
                catch (Exception x)
                {
                    //Task wurde nie gestartet oder schon beendet
                    label1.Text = "Motorsteuerung wurde nicht ordnungsgemäß gestoppt.";
                }
                button1.Enabled = true;
                button2.Enabled = false;
            }
        }
    }
    //##################

    VS schreibt, das es eine bereits abgeschlossene Anwendung nicht neustarten kann. Wenn ich aber nach dem TaskToken.Cancel(); mit Wait warte und anschließend dispose, um die ressourcen frei zugeben, bleibt mein UI im Wait hängen obwohl mein task durch die exception vollständig durchlaufen sein sollte. Außerdem wird vor dem .Start ein neues Task-Objekt erzeugt, was meiner Meinung nach wieder einen komplett neuen (vom vorherigen Task unabhängigen) Task erzeugen sollte.

    Hat vielleicht jemand eine Idee an was das liegen kann?

     

     

    Vielen Dank im Voraus,

    Markus

    Freitag, 19. November 2010 09:43

Alle Antworten

  • Hallo Markus,

    erstelle jeweils eine neue CancellationTokenSource, denn die "alte" ist nach dem
    Abbruch nicht weiter verwendbar, siehe Aufgabenabbruch .

    Eine detailliertere Beschreibung dazu findest Du unter: .NET 4 Cancellation Framework
    Im Abschnitt "Details" findest Du weitergehende Erläuterungen, warum das mit dem Weiternutzen keinen Sinn gibt.

    Einige kleinere Anmerkungen:
    Den zusätzlichen Delegate kannst Du Dir ersparen in dem Du Lambdas verwendest,
    die man bei der TPL sehr häufig benötigt.
    Für die den erforderlichen Invoke Aufruf kann man das mit einer Erweiterungsmethode kombinieren,
    siehe Control Invoke Pattern Using Lambdas

    Gruß Elmar

    Freitag, 19. November 2010 23:13
    Beantworter
  • Hallo,

    das Cancellation Token ist nicht mein Problem. Mein Problem ist, dass die Tasks eine bestimmte Tätigkeit machen/nicht-machen müssen, je nach dem was durch die Buttons verlangt wird.  Da man die Tasks anscheinend nicht neu starten, habe ich mir gedacht sie können einfach in einer Endlosschleife laufen und überprüfen bei jedem durchgang flags in denen steht, ob sie ihre funktion (z.B.: Daten schicken) erfüllen oder schlafen sollen. Die Tasks arbeiten aber teilweise mit wartenden funktionen (ich glaub die heißen Blockingcalls) und können, weil sie sich in einer Endlosscheife befinden, nicht sofort auf änderungen der flags durch die Buttons reagieren.Die GUI und die Tasks arbeiten aneinander vorbei und die Information von Usereingaben ist nicht konsistent.

    Gibt es eine elegantere Lösung als mit etlichen Flags oder eine möglichkeit einen in einer Endlosschleife befindlichen Task neu zu starten? Es muss doch möglich sein, denn immerhin gibt es ja Programm, die das auch können.

    Kann es sein, dass wenn der Task in einer Endlosschleife läuft nicht auf das Dispose() reagiert/regieren kann?

     

    Lg,

    Markus

    Donnerstag, 25. November 2010 12:09
  • Hall Markus,

    das CancellationToken ist dafür gedacht, was der Name schon sagt:
    Eine Abbruchsteuerung über (mehrere, beliebig viele) Tasks kontrolliert durchzuführen
    (der bereits genannte englische Blog-Artikel vergleicht u. a. das Konzept mit anderen
    wie dem BackgroundWorker). Mehr sollte und kann ein CancellationToken nicht tun.

    Deine Annahme, das man einen Task nicht neu starten kann, stimmt so nicht.
    Ein Task ist wie der deutsche Name "Aufgabe" schon sagt, ein einzelner Arbeitsschritt.
    Ist der erledigt, sollte sich die Aufgabe beenden.

    Laufen wiederum Tasks parallel, wobei der eine auf Eingaben des anderen wartet,
    so sollte man dazu eine der threadsicheren Auflistungen verwenden, z. B. die BlockingCollection
    Dort findest Du wieder das CancellationToken, dass dann dafür sorgt,
    das der Abbruch kontrolliert stattfindet, wenn der Produzent keine Eingaben
    mehr liefern kann/will oder alle gesamte Vorgang (alle Tasks) beendet werden soll.

    Dabei wird dann die Aufgabe blockiert ohne dabei in eine Endlosschleife (busy wait)
    zu gehen. Denn in der TPL sind die Tasks nicht direkt an einen Thread gebunden,
    sondern werden auf eine Anzahl Worker Threads dynamisch verteilt.

    Typische Lösungwege werden beschrieben unter Fortsetzungsaufgaben und
    Geschachtelte Aufgaben und untergeordnete Aufgaben

    Wenn Du Deinen Datenfluss näher beschreibst, so kann ich Dir
    u. U.  einige mögliche Lösungsansätze dafür geben.

    Gruß Elmar

    Freitag, 26. November 2010 10:21
    Beantworter