WP8 – TaskScheduler – Aufgaben in periodischen Abständen erledigen

Hallo Leute,

mit diesem Blog-Eintrag möchte ich euch zeigen, wie Ihr einen TaskScheduler hinzufügen könnt. Im konkreten Beispiel möchte ich für eine DB-Abfrage die in meinem letzten Blog-Eintrag erzeugte IconicTile aktualisieren.

An dieser Stelle möchte ich mich wieder einmal bei Channel9 bedanken, welcher einen hervorragenden Video-Blog zu diesem Thema verfasst hat.

Zunächst einmal muss man verstehen, dass ein TaskScheduler/Background Agent eine eigene, eingeschränkte App ist, welche auch dann läuft, wenn die App selbst nicht läuft! Ein Scheduled Task bleibt für ca. 2 Wochen „aktiv“, bevor er vom System entfernt wird. Schlägt der Task zwei Mal fehl, wird er ebenfalls vom System entfernt. Das bedeutet für uns, dass wir den Task immer wieder hinzufügen müssen, z.B. beim Aufrufen des Hauptmenüs oder der Startseite der App. Man kann natürlich auch einem Event wie z.B. Application_Launching subscriben.

Als erstes muss man mal ein neues Projekt seiner Solution hinzufügen. Visual Studio bietet hier die Vorlage „Windows Phone Schedules Task Agent“ an:

2013-12-10 11_10_31-New Project

Dieses neue Projekt enthält nun eine Klasse, die ScheduledAgent.cs. Diese Klasse kann im Großen und Ganzen unverändert bleiben, interessant für uns ist lediglich die „OnInvoke“ Methode.

 protected async override void OnInvoke(ScheduledTask task)
        {
            try
            {
                // TODO: Your code
                NotifyComplete();
            }
            catch (Exception)
            {
                Abort();
                throw;
            }
        }

Wichtig ist hier, dass jeder erfolgreiche Task zum Abschluss die „NotifyComplete()“ Methode aufruft, da sich der Task sonst nicht beendet und der nächste Intervall niemals schlagend wird. Im Fehlerfall gilt das selbe für „Abort()“. Achten sollte man auch auf das „async“ Keyword. Alle eigenen Methodenaufrufe müssen ebenfalls asynchron sein.

Doch zuerst muss man der eigenen (Haupt-)App mitteilen, dass es den neuen TaskScheduler überhaupt gibt und Tasks erzeugen. Dafür öffnet man die WMAppManifest.xml im XML-Editor. Sucht die Node „Tasks“ und fügt einen ExtendedTask hinzu:

<Tasks>
      <DefaultTask Name="_default" NavigationPage="xxx.xaml" />
      <ExtendedTask Name="BackgroundTask">
        <BackgroundServiceAgent Specifier="ScheduledTaskAgent" Name="PeriodicAgent" Source="namespace" Type="namespace.ScheduledAgent" />
      </ExtendedTask>
    </Tasks>

Ersetzt bitte „namespace“ mit dem Namespace eures TaskSchedulers. Als nächste fügt eurer App eine Referenz auf das TaskScheduler Projekt hinzu (Rechtsklick auf References -> Add Reference -> Solution).

Ist das erledigt, können wir uns auf das Erzeugen von Tasks konzentrieren. Ich habe dafür meiner App.xaml.cs eine Methode „StartPeriodicAgent()“ hinzugefügt:

 /// <summary>
        /// Starts the periodic agent.
        /// </summary>
        public static void StartPeriodicAgent()
        {
            string taskName = "BackgroundTask";
            PeriodicTask oldTastk = ScheduledActionService.Find(taskName) as PeriodicTask;

            if (oldTastk != null)
            {
                ScheduledActionService.Remove(taskName);
            }

            PeriodicTask task = new PeriodicTask(taskName);
            task.Description = "Aktualisiert die App-Kachel für offene/genehmigte Urlaube.";

            ScheduledActionService.Add(task);

            if(Debugger.IsAttached)
                ScheduledActionService.LaunchForTest(task.Name, TimeSpan.FromSeconds(30));
            
        }

Wichtig ist hier, dass der TaskName der gleiche ist, wie vorhin in der WMAppManifext.xml. Dadurch wird der Anwendung klargemacht, dass der eigene TaskScheduler verwendet werden muss. Der restliche Code ist eigentlich selbsterklärend. Erwähnenswert sind vielleicht die letzten zwei Zeilen, welche den Effekt haben, dass der Task nach 30 Sekunden und nicht erst nach ca. 30 Minuten aufgerufen wird. Diese Methode ist lt. Microsoft lediglich für Debugging-Zwecke gedacht – daher die vorhergehende If-Condition.

Diese Methode könnt Ihr z.B. in Konstruktor der App.xaml.cs aufrufen.

Nun springen wir zurück zur ScheduledAgent.cs, zur Methode OnInvoke. Ich rufe hier die Methode UpdateMainTile auf, um eine IconicTile (Kachel) zu aktualisieren:

 protected async override void OnInvoke(ScheduledTask task)
        {
            try
            {
                await Tile.UpdateMainTile(5); // TODO: Eigene Methode zum Berechnen des Counts verwenden!

                if (Debugger.IsAttached)
                    ScheduledActionService.LaunchForTest(task.Name, TimeSpan.FromSeconds(30));
                
                NotifyComplete();
            }
            catch (Exception)
            {
                Abort();
                throw;
            }
        }

Wichtig ist hier das „await“-Keword vorm Aufruf von UpdateMainTile(). Auch auf der Methode selbst ist noch etwas zu ergänzen, denn auch diese muss klarerweise async und vom Typ „Task“ sein:

 public async static Task UpdateMainTile(string title, int count)
        {
            await Task.Factory.StartNew(() =>
            {
                //Get application's main tile
                var mainTile = ShellTile.ActiveTiles.FirstOrDefault();


                if (null != mainTile)
                {
                    IconicTileData tileData = new IconicTileData()
                    {
                        Count = count,
                        BackgroundColor = Color.FromArgb(0, 151, 193, 1),
                        Title = title,
                        IconImage = new Uri("/Assets/Tiles/IconicTileMediumLarge.png", UriKind.RelativeOrAbsolute),
                        SmallIconImage = new Uri("/Assets/IconicTileSmall.png", UriKind.RelativeOrAbsolute),
                        WideContent1 = "urlaube in genehmigung",
                    };

                    mainTile.Update(tileData);
                }

            });
        }

Um nicht zuviel Code umstellen zu müssen, kann man auch einfach mit await Task.Factory.StartNew() arbeiten. Wichtig ist jedenfalls, dass sämtliche aufgerufenen Methoden „async“ und vom Typ „Task“ sind.

Cheers,
Christian

Leave a reply