6

Jan

Ancora sui background agents: il ciclo di vita

Come ho anticipato in un post di qualche giorno fa, nella beta 2 sono stati introdotti diversi cambiamenti importanti riguardo al ciclo di vita dei background agents, i servizi che possono essere associati alla nostra applicazione ed eseguire operazioni anche quando questa non è in esecuzione.

In questo post esamineremo perciò tutte le novità e impareremo come gestire tutti i casi particolari che si possono verificare durante l’esecuzione.

LaunchForTest: un nuovo metodo per eseguire i background agent

Già nel post precedente avevo anticipato come la beta 2 avesse introdotto un nuovo metodo per lanciare i background agent, esposto dallo ScheduledActionService e chiamato LaunchForTest. Il vantaggio di questo nuovo metodo è duplice:

  • Da una parte, il meccanismo di testing e debugging dei background agent nella beta 1 era un po’ macchinoso e a volte capitava che l’agent non venisse eseguito.
  • Dall’altra, i background agents hanno un tetto massimo di 5 MB di memoria che possono occupare. Se testiamo un background agent con il debugger collegato, parte della memoria viene occupata dal debugger stesso, rendendo perciò non realistici i nostri test. Il metodo LaunchForTest invece ci permette di eseguire il background agent anche a debugger scollegato.

Il metodo LaunchForTest accetta come parametri il nome dell’agent (che deve essere già stato aggiunto a quelli schedulati nel sistema) e dopo quanto tempo l’agent deve essere eseguito (sotto forma di TimeSpan). Grazie all’utilizzo delle direttive di compilazione, possiamo far sì che il metodo venga eseguito, ad esempio, solo se stiamo compilando l’applicazione in modalità Debug. In questo modo, ci assicuriamo che quando avremo l’applicazione sarà pronta per essere pubblicata sul Marketplace questo codice non verrà eseguito (dato che devono per forza essere compilate in modalità Release, pena il fallimento del processo di certificazione).

Ecco un esempio di codice:

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

      ScheduledActionService.Add(task);


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

La cosa interessante è che possiamo utilizzare il metodo LaunchForTest anche all’interno di un background agent, per simulare esecuzioni multiple senza dover attendere lo scheduling da parte del sistema operativo. Ecco un esempio di background agent che, terminato il suo compito (ovvero mostrare una notifica toast), pianifica una nuova esecuzione dello stesso dopo 1 minuto:

protected override void OnInvoke(ScheduledTask task)
      {

      ShellToast toast = new ShellToast
      {
      Title = "Title",
      Content = "Content"
      };
      toast.Show();

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

      NotifyComplete();

      }   

Il ciclo di vita di un background agent: il background agent viene disabilitatoimage

Se ricordate quanto spiegato nella prima serie di post che ho pubblicato sui background agent, nell’hub Settings di Mango è stato introdotto un nuovo pannello, chiamato Background tasks, in cui l’utente può gestire i background agents che ha installato sul device. Nello specifico, viene data la possibilità di disabilitare i background agents di tipo periodic, senza costringere l’utente a dover disinstallare l’applicazione per fermare la loro esecuzione.

Ma cosa succede quando un background agent viene disabilitato? La proprietà IsEnabled del nostro task viene messa a false: quando si verifica questa condizione, non dovremmo cercare di aggiungere nuovamente il task allo ScheduledActionService, perchè violeremmo l’esplicita richiesta dell’utente. L’utente però ha a disposizione un checbox nel pannello di controllo che gli consente di riabilitare il background agent all’avvio successivo dell’applicazione.

Purtroppo al momento non esiste modo di sapere da codice se l’utente ha selezionato o meno quel checkbox. Quello che dovremo fare perciò non è tanto controllare il valore della proprietà IsEnabled, ma eseguire l’operazione di schedulazione dell’agent all’interno di un blocco try / catch. Se infatti l’utente non ha abilitato il checkbox e si cerca di schedulare il task, si scatenerà un’eccezione di tipo InvalidOperationException con il messaggio BNS Error: The action is disabled. Quando si verifica questa situazione sta a noi decidere qual è il comportamento migliore: ad esempio, potremmo mostrare un messaggio all’utente avvisandolo che il background agent è stato disabilitato e che, se vuole, può andare nei Settings a riabilitarlo.

Gli esempi di codice pubblicati su MSDN adottano un altro approccio: se la proprietà IsEnabled è impostata a false, l’agent non viene schedulato. Personalmente non vi consiglio questa soluzione(anche se è sicuramente più elegante), perchè non tiene conto del fatto che l’utente possa scegliere di riabilitarlo tramite il checkbox: il risultato sarebbe  che l’agent non verrebbe mai più rischedulato, a meno che l’applicazione non venga disinstallata e reinstallata.

Il ciclo di vita di un background agent: il metodo Abort

Ricordate il metodo NotifyComplete, che deve essere chiamato il prima possibile e che comunica al sistema operativo che il background agent ha terminato la sua esecuzione in maniera corretta?

Bene, ora abbiamo a disposizione un nuovo metodo per gestire il ciclo di vita di un agent, chiamato Abort, che possiamo invocare invece nel momento in cui qualcosa è andato storto e decidiamo che, dato che non sussistono le condizioni necessarie, il background agent non deve più essere eseguito fino a che la nostra applicazione non verrà riaperta e il problema risolto.

Quando il metodo Abort viene chiamato, la proprietà IsScheduled del nostro task viene impostata a false. L’agent risulterà ancora abilitato e attivo, ma non verrà più eseguito automaticamente fino a nuovo ordine. Come comportarci quindi nella nostra applicazione? Se troviamo la proprietà IsScheduled a false ma IsEnabled a true, ci troviamo nella condizione in cui l’agent è stato volontariamente fermato. In più, la proprietà LastExitReason sarà impostata a Abort. In questo caso possiamo procedere a rischedulare il task, dato che siamo stati a noi a fermarne l’esecuzione e non l’utente, disabilitandolo dal pannello di controllo.

Ci sono altre due condizioni che possono portare la proprietà IsScheduled a false (in questo caso però la proprietà LastExitReason avrà un valore differente):

  • L’agent è scaduto, ovvero la sua proprietà ExpirationTime è stata raggiunta senza che venisse rinnovato. In questo caso, possiamo procedere a rischedularlo.
  • L’utente ha disabilitato l’agent dal pannello: in questo caso, però, come abbiamo visto prima anche la proprietà IsEnabled sarà a false.

Il ciclo di vita di un background agent: gestire le eccezioni

Esiste però un’altra condizione che può disabilitare temporaneamente lo scheduling di un background agent, impostando la proprietà IsScheduled a false: quando l’esecuzione del background agent fallisce a causa di un’eccezione (la LastExitReason del task è UnhandledException) oppure perchè ha superato la massima quantità di memoria utilizzabile (la LastExitReason del task è MemoryQuotaExceeded).

In questo caso, dopo due esecuzioni consecutive fallite per uno di questi motivi, lo scheduling viene automaticamente fermato: la proprietà IsScheduled viene perciò impostata su false.

Il consiglio, perciò (soprattutto se state usando background agent al limite come utilizzo della memoria o possibilità di fallimento), è quello di controllare la proprietà LastExitReason e, in caso di fallimento, rischedulare il background agent solo se si è ragionevolmente sicuri che alla prossima esecuzione il problema non si presenterà.

Nel prossimo post: un esempio concreto

Nel prossimo post adatteremo l’applicazione che abbiamo usato in precedenza per testare i background agent per gestire il ciclo di vita completo, andando a gestire in maniera corretta le varie casistiche presentate in questo articolo.

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