7

Jan

Il ciclo di vita dei background agents: un esempio concreto

Nel post precedente abbiamo visto come è strutturato il ciclo di vita di un background agent, soprattutto alla luce delle novità introdotte nella beta 2 dei tools per Mango. Ci eravamo lasciato affrontando nuovi concetti quali il metodo LaunchAndTest e le proprietà IsEnabled e IsScheduled esposte dalle classi PeriodicTask e ResourceIntensiveTask. Ora vediamo di mettere insieme tutti questi concetti per realizzare un’applicazione reale.

Passiamo ai fatti: l’applicazione

L’applicazione che andremo a realizzare è molto semplice, ma è molto utile per mettere alla prova i concetti che abbiamo imparato. La base sarà la stessa che abbiamo usato nella serie di tutorial precedenti sui background agent: un’applicazione collegata ad un agent in grado di mostrare notifiche toast di tipo locale. La differenza è che questa volta daremo all’utente la possibilità, tramite un form, di impostare titolo e contenuto della notifica. Egli sarà però in grado di pianificare comunque l’agent, anche senza impostare questi valori: in tal caso, però, ne interromperemo la schedulazione chiamando il metodo Abort, dato che non sussistono le condizioni perchè questo continui ad essere eseguito.

La UI della nostra applicazione è molto semplice: un form con due etichette e i due relativi TextBox (per inserire titolo e contenuto della notifica) e un pulsante, che useremo per schedulare l’agent.


      
      
      
      
      
      

Vediamo il codice che viene eseguito nel momento in cui viene premuto il pulsante Create agent.

private void btnCreateAgent_Click(object sender, System.Windows.RoutedEventArgs e)
      {
      if (!string.IsNullOrEmpty(txtTitle.Text))
      {
      IsolatedStorageSettings.ApplicationSettings.Add("Title", txtTitle.Text);
      IsolatedStorageSettings.ApplicationSettings.Add("Subtitle", txtSubtitle.Text);
      }

      bool isEnabled = true;

      PeriodicTask action = ScheduledActionService.Find("TestAgent") as PeriodicTask;
      if (action == null || (!action.IsScheduled))
      {
      if (action != null)
      ScheduledActionService.Remove(action.Name);

      PeriodicTask task = new PeriodicTask("TestAgent")
      {
      Description = "Test task",
      ExpirationTime = DateTime.Now.AddDays(7)
      };

      try
      {
      ScheduledActionService.Add(task);
      }
      catch (InvalidOperationException exc)
      {
      if (exc.Message.Contains("BNS Error: The action is disabled"))
      MessageBox.Show("The agent has been disabled. Go to Settings to enable it.");
      isEnabled = false;
      }
      }

      #if DEBUG
      if (isEnabled)
      ScheduledActionService.LaunchForTest("TestAgent", new TimeSpan(0, 0, 10));
      #endif
      }
      }

Innanzitutto, andiamo a salvare nell’Isolated Storage i valori inseriti nelle due caselle di testo, in modo che il background agent possa recuperare questa informazione. Dopodiché, sfruttando il metodo Find dello ScheduledActionService, andiamo a recuperare il nostro task (identificato dal nome TestAgent), e lo convertiamo in un PeriodicTask.

Dopodichè controlliamo lo stato del task: se questo è a null oppure non è schedulato (IsScheduled = false) allora lo riprogrammiamo. Quando ci possiamo trovare in questa condizione?

  • Quando l’agent non è mai stato programmato (tipicamente alla prima esecuzione, quindi l’oggetto recuperato dallo ScheduledActionService è a null).
  • Quando l’agent è scaduto.
  • Quando l’agent è stato interrotto perchè abbiamo chiamato il metodo Abort.

Ovviamente, in un caso reale se la proprietà LastExitReason dell’agent fosse su Aborted, dovremmo assicurarci che le condizioni che abbiamo identificato come non più idonee per l’esecuzione dell’agent siano state risolte. Nel nostro esempio, potremmo assicurarci ad esempio che l’agent venga rischedulato solo nel caso in cui le proprietà Title e Content siano state valorizzate. In questo caso non lo abbiamo fatto per dare la possibilità di testare il metodo Abort, che vedremo tra pochissimo.

Come spiegato nel post precedente, abbiamo incluso il metodo ScheduledActionService.Add dentro un blocco try / catch: questo perchè l’utente potrebbe aver richiesto la disabilitazione permanente dell’agent, perciò questa operazione potrebbe scatenare un’eccezione di tipo InvalidOperationException. Quello che facciamo è andare a mostrare un messaggio all’utente, avvisandolo che il background agent è stato disabilitato e che, se vuole riabilitarlo, può farlo dall’hub Settings. Lo facciamo però solo se il messaggio dell’eccezione è BNS Error: The action is disabled; questo perchè l’eccezione di tipo InvalidOperationException si possono verificare anche in altri casi (ad esempio, è stato raggiunto il massimo numero di background agent installabili nel device).

Il background agent

Come anticipato all’inizio, il background agent si limiterà a mostrare una notifica di tipo toast. La novità rispetto al progetto di esempio di qualche settimana fa, però, è che questa volta andremo a chiamare il metodo Abort nel caso in cui l’utente non abbia valorizzato le proprietà Title e Content.

protected override void OnInvoke(ScheduledTask task)
      {
      if (IsolatedStorageSettings.ApplicationSettings.Contains("Title"))
      {
      ShellToast toast = new ShellToast
      {
      Title = IsolatedStorageSettings.ApplicationSettings["Title"].ToString(),
      Content = IsolatedStorageSettings.ApplicationSettings["Subtitle"].ToString()
      };
      toast.Show();

      #if DEBUG
      ScheduledActionService.LaunchForTest(task.Name, new TimeSpan(0, 1, 0));
      #endif
      NotifyComplete();
      }
      else
      Abort();
      }   

Se nell’IsolatedStorage abbiamo salvato una chiave chiamata Title, allora eseguiamo il background agent in maniera tradizionale: mostriamo la notifica e chiamiamo il metodo NotifyComplete al termine. Notiamo inoltre che, se siamo in debug, ripianifichiamo una nuova esecuzione dell’agent stesso dopo 1 minuto.

Se invece tale chiave non esiste, allora chiamiamo il metodo Abort.

Facciamo un po’ di test

Rifacendoci ai vari scenari presentati nel post precedente, andiamo a vedere il comportamento del nostro agent nelle varie condizioni. Come prima cosa, impostiamo due breakpoint: uno nell’applicazione, nel momento in cui viene premuto il pulsante, e uno nell’agent, nel momento in cui viene eseguito il metodo OnInvoke.

L’esecuzione va a buon fine

Lanciamo l’applicazione, inseriamo dei valori nei campi Title e Content e premiamo il pulsante Create agent: essendo la prima esecuzione, il metodo ScheduledActionService.Find restituirà null perciò l’agent verrà pianificato normalmente. Ora usciamo dall’applicazione e, nel giro di 10 secondi (l’intervallo di tempo passato come parametro del metodo LaunchAndTest), verrà eseguito il background agent. Dato che nell’Isolated Storage vengono trovate le informazioni inserite dall’utente nel form, la notifica toast verrà visualizzata a video e verrà chiamato il metodo NotifyComplete. Infine, un minuto dopo circa il background agent entrerà di nuovo in azione, dato che prima di terminarlo abbiamo riutilizzato il metodo LaunchAndTest.

Se ora riapriamo l’applicazione e ripremiamo il pulsante, potremo notare che la creazione e pianificazione del task verrà saltata: questo perchè la proprietà IsScheduled sarà ancora a true, dato che l’agent non è scaduto e l’ultima esecuzione è andata a buon fine.

Il task viene terminato

Ora disinstalliamo l’applicazione e ripremiamo F5 in Visual Studio: questa volta premiamo il pulsante Create agent senza inserire nulla nel form. Al primo avvio il comportamento sarà lo stesso di prima: il task, non esistendo, verrà creato e pianificato. L’unica differenza è che nell’Isolated Storage non avremo salvato niente: il risultata sarà che, dopo 10 secondi, il background agent verrà eseguito ma, facendo debug step by step grazie al breakpoint che abbiamo inserito, potremo verificare come l’esecuzione passi direttamente al metodo Abort.

Ora, se riapriamo l’applicazione e mettiamo un quick watch sull’oggetto action di tipo PeriodicTask, potremo verificare come la proprietà IsEnabled sia ancora a true, ma la proprietà IsScheduled sia questa volta a false. In più, se guardiamo il valore della proprietà LastExitReason avremo la conferma che tutto è andato come ci aspettavamo. Se continuiamo con il debug step by step ci aspettiamo che il background agent venga nuovamente rischedulato: questo perchè è stato semplicemente “sospeso” in attesa che si ricreassero le condizioni necessarie per l’esecuzione.

L’agent viene annullato dall’utente

Senza disinstallare questa volta l’applicazione, andiamo sull’emulatore, entriamo nell’hub Settings e, alla voce Applications, selezioniamo Background Tasks. Troveremo la nostra applicazione, il cui stato sarà su On. Facciamo tap e premiamo il pulsante Turn off. Ora ripetiamo il giro di prima: premiamo F5 in Visual Studio, lanciamo l’applicazione e premiamo il pulsante Create agent. Questa volta otterremo un comportamento ancora diverso: la proprietà IsEnabled sarà a false, ciò significa che l’utente ha esplicitamente richiesto la sospensione dell’agent. Per questo motivo, quando andremo a chiamare il metodo ScheduledActionService.Add si scatenerà un’eccezione: entreremo perciò nel blocco catch e verrà mostrato a video il messaggio.

Adesso ripetiamo quest’ultimo test: prima però torniamo nella sezione Settings, facciamo tap sulla nostra applicazione e abilitiamo il checkbox Turn background tasks back for this time the next time I open it. Ora rilanciamo l’applicazione da Visual Studio: questa volta il metodo ScheduledActionService.Add non farà scattare alcuna eccezione e il task verrà schedulato normalmente.

In conclusione

Come vedete, l’argomento dei background agent si è dimostrato molto articolato e complesso. In realtà, i concetti base da capire e tenere a mente sono semplici: sono tante però le situazioni e i casi da gestire. Spero che questa serie di articoli vi sia stato d’aiuto per chiarivi le idee! Di seguito trovate il link per scaricare il progetto di esempio con cui abbiamo lavorato in questo post.

by Il blog di Matteo Pagani on 1/7/2012
Post archive