Guest post: le nuove funzionalità di esecuzione in background di Windows 10

Questo post è stato scritto da Matteo Pagani, Support Engineer in Microsoft per il programma AppConsult

Se avete già avuto occasione, in passato, di sviluppare Universal Windows app per Windows o Windows Phone 8.1, il concetto di "ciclo di vita dell'applicazione" dovrebbe esservi famigliare.

Rispetto al mondo desktop tradizionale, dove il ciclo di vita delle applicazioni è piuttosto semplice (sostanzialmente, un'applicazione rimane in esecuzione fintanto che è aperta), nel mondo mobile le cose sono più complicate. Un dispositivo mobile (come uno smartphone), infatti, deve far fronte ad una serie di scenari che non sono tipici del mondo desktop, quali il consumo di batteria, la quantità di memoria limitata, ecc.

Di conseguenza, le Universal Windows app (sia su 8.1 che su 10) adottano un ciclo di vita che può essere sintetizzato con l'immagine seguente:

Nel momento in cui l'applicazione viene aperta, questa entra in uno stato di esecuzione chiamato running, che le dà la possibilità di sfruttare memoria, CPU, rete, sensori e quant'altro sia disponibile sul device. Nel momento in cui viene sospesa (l'utente preme il pulsante Start, oppure riceve una notifica proveniente da un'altra applicazione e decide di aprirla) questa dopo 5 secondi viene "congelata" in memoria: ciò significa che il processo sarà mantenuto nella memoria RAM del dispositivo, ma non sarà in grado di eseguire alcuna operazione. Di conseguenza, tutte le risorse del dispositivo (CPU, fotocamera, sensori, ecc.) saranno liberate e pronte per essere utilizzate dalla nuova applicazione. Nel momento in cui l'utente sceglie di riaprire l'applicazione, ecco che si verifica l'evento di resuming: il processo viene "risvegliato" e l'applicazione può tornare a sfruttare le risorse messe a disposizione del dispositivo. Può capitare, però, che le risorse di sistema inizino ad esaurirsi: ad esempio, l'utente potrebbe aprire molte applicazioni, oppure averne sospese una o due molto onerose in termini di memoria occupata (come un gioco). In questo caso, all'apertura di una nuova applicazione, il dispositivo potrebbe non avere memoria sufficiente per gestirla: ecco, perciò, che il sistema operativo ha la possibilità di terminare le applicazioni più vecchie (in termini di frequenza di utilizzo) per liberare memoria. In questo scenario è compito dello sviluppatore, in fase di sospensione, salvare lo stato dell'applicazione in modo che, quando l'utente deciderà di riaprirla, la ritrovi esattamente come l'aveva lasciata: per l'utente finale, la terminazione del sistema operativo dovrebbe essere completamente trasparente.

Eseguire operazioni in background: i background task

Come potete intuire dalla spiegazione precedente, le applicazioni non hanno la possibilità di eseguire operazioni quando sono sospese. Il processo, infatti, è congelato e, di conseguenza, non può eseguire alcun tipo di attività che richieda l'uso di CPU, connettività, ecc.

Di conseguenza, per permettere agli sviluppatori di gestire la necessità di eseguire operazioni anche quando l'applicazione è sospesa, il Windows Runtime ha introdotto il concetto di background task: si tratta di progetti indipendenti, che fanno parte della stessa soluzione che contiene l'applicazione principale, i quali contengono blocchi di codice che vengono eseguiti dal sistema operativo anche quando l'applicazione non è in esecuzione.

La struttura di un background task è molto semplice:

using Windows.ApplicationModel.Background;

 

namespace Tasks

{

public sealed class ExampleBackgroundTask : IBackgroundTask

{

public void Run(IBackgroundTaskInstance taskInstance)

{

 

}

}

}

Semplificando, si tratta semplicemente di creare una classe che implementi l'interfaccia IBackgroundTask e che definisca il metodo Run() , che viene invocato nel momento in cui task viene attivato.

I background task sono dei progetti di tipo Windows Runtime Component e sono legati al concetto di trigger, ovvero degli eventi che possono far scatenare l'esecuzione del task: può essere un trigger periodico (ogni 30 minuti, ad esempio), legato ad un evento di sistema (l'utente ha bloccato / sbloccato il device) o a alla comunicazione con altri dispositivi (ad esempio, esistono una serie di trigger legati all'interazione con dispositivi Bluetooth Low Energy).

Non approfondirò l'argomento background task all'interno di questo post, dato che il loro funzionamento è rimasto sostanzialmente invariato nel passaggio da Windows 8.1 a Windows 10. Sono state aggiunte, infatti, numerose nuove tipologie di trigger, ma i concetti basi che ne guidano lo sviluppo sono i medesimi. Se siete interessati all'argomento, potete fare riferimento alla documentazione MSDN disponibile all'indirizzo https://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh977056.aspx

Mantenere l'applicazione in background anche quando viene sospesa

La vera novità introdotta in Windows 10 e oggetto di questo post è la classe ExtendedExecutionSession, che vi consente di supportare due nuovi scenari:

  1. La vostra applicazione ha bisogno di salvare dei dati o terminare un'operazione in corso prima di essere sospesa e i 5 secondi assegnati dal sistema operativo non sono sufficienti.
  2. La vostra applicazione ha bisogno di continuare l'esecuzione anche se viene sospesa, ad esempio per tracciare in maniera continuativa la posizione dell'utente (navigatore satellitare, applicazioni di fitness, ecc.)

Vediamo in dettaglio come gestire entrambi gli scenari.

Terminare un'operazione in corso

La gestione di questo scenario avviene all'interno del metodo che gestisce la sospensione: la nostra applicazione sta per essere sospesa e vogliamo la possibilità di terminare l'operazione in corso (ad esempio, l'upload di un file sul server). Di conseguenza, il punto di partenza è la classe App, dichiarata nel file App.xaml.cs, presente in tutti i progetti di tipo Universal Windows app.

All'interno troverete un'implementazione base del metodo OnSuspending() , così definito:

private void OnSuspending(object sender, SuspendingEventArgs e)

{

var deferral = e.SuspendingOperation.GetDeferral();

 

deferral.Complete();

}

Tale blocco di codice, all'atto pratico, non esegue nulla: si limita a richiedere un deferral, che dà la possibilità di eseguire operazioni asincrone (quindi, sfruttando le keyword async e await) all'interno dell'evento di sospensione. Senza l'uso del deferral, l'avvio di un'operazione asincrona su un thread differente da quello principale potrebbe causare la sospensione immediata dell'applicazione.

All'interno di questo blocco dobbiamo andare a sfruttare la classe ExtendedExecutionSession per richiedere un'estensione di tempo, così da avere la possibilità di terminare l'operazione. Ecco un esempio di codice:

private async void OnSuspending(object sender, SuspendingEventArgs e)

{

var deferral = e.SuspendingOperation.GetDeferral();

 

var extendedSession = new ExtendedExecutionSession();

extendedSession.Reason = ExtendedExecutionReason.SavingData;

extendedSession.Description = "Complete synchronization";

 

ExtendedExecutionResult result = await extendedSession.RequestExtensionAsync();

if (result == ExtendedExecutionResult.Allowed)

{

UploadCompleteData();

}

else

{

UploadBasicData();

}

 

deferral.Complete();

}

Nel momento in cui creiamo un nuovo oggetto di tipo ExtendedExecutionSession, dobbiamo specificare:

  1. La motivazione per cui vogliamo che la nostra operazione continui anche dopo la sospensione. Nel nostro scenario, dobbiamo specificare il tipo SavingData dell'enumeratore ExtendedExecutionReason.
  2. Una descrizione che riassume brevemente il tipo di operazione che andremo ad eseguire. E' una semplice stringa testuale.

Dopodichè possiamo chiamare il metodo RequestExtensionsAsync() . Come si evince dal nome stesso, tale metodo non ci garantisce che la richiesta andrà a buon fine: il sistema operativo, infatti, potrebbe decidere di negarla, in base alle risorse disponibili in quel momento. È importante, perciò, verificare il risultato della chiamata, che è di tipo ExtendedExecutionResult. Solo nel caso in cui sia di tipo Allowed, allora siamo autorizzati ad eseguire un'operazione più complessa, dato che l'applicazione non sarà sospesa dopo 5 secondi. In caso contrario, invece, dobbiamo gestire il fatto che il sistema operativo ci abbia negato l'esecuzione in background: di conseguenza, dobbiamo rispettare il vincolo di 5 secondi prima che qualsiasi processo attivo venga terminato.

Tale approccio deve essere usato per gestire operazioni che abbiano, comunque, una durata non eccessiva: il sistema operativo, infatti, in caso le risorse si stiano esaurendo potrebbe terminare preventivamente l'attività in corso.

Mantenere l'applicazione attiva in background

La seconda funzionalità garantita dalla classe ExtendedExecutionSession offre più flessibilità rispetto allo scenario precedente, ma anche più vincoli. Con questo approccio, infatti, nel momento in cui l'applicazione viene sospesa, in realtà, questa viene mantenuta attiva in background: il processo non viene congelato, ma rimane attivo e in grado di eseguire operazioni, reagire agli eventi, ecc. È come se l'applicazione fosse in esecuzione in foreground, solamente non visibile. Si tratta di una funzionalità molto utile in particolar modo per le applicazioni che fanno un uso intensivo del tracciamento della posizione dell'utente: un'applicazione dedicata al fitness, ad esempio, potrebbe continuare a tracciare la corsa dell'utente anche se il telefono si trova bloccato in tasca.

È proprio per questo motivo che la classe ExtendedExecutionSession identifica questo approccio assegnando il valore LocationTracking alla proprietà Reason. Inoltre, per poterla sfruttare è necessario che la capability Location, all'interno del file di manifest, sia abilitata. In realtà, il sistema operativo non vi obbliga ad usare le API di geolocalizzazione per mantenere attiva questa modalità: è sconsigliato, però, utilizzarla per scopi differenti dal tracciamento della posizione dell'utente, in virtù del fatto che dareste un'informazione errata all'utente. Quanto questi consulterà, infatti, la pagina sullo Store della vostra applicazione, troverà tra l'elenco di funzionalità richieste il tracciamento della posizione, anche se non la utilizzate realmente.

Rispetto allo scenario precedente di salvataggio dei dati, esiste un'importante differenza nell'utilizzo della classe ExtendedExecutionSession: se, nell'esempio di codice visto poco fa, la utilizzavamo all'interno del metodo OnSuspending() della classe App, in questo caso invece dobbiamo richiedere il permesso al sistema operativo di continuare l'operazione in background il prima possibile. Un buon punto, ad esempio, dove inizializzarla è all'interno dell'evento OnNavigatedTo() della pagina principale dell'applicazione, come nell'esempio seguente:

protected override async void OnNavigatedTo(NavigationEventArgs e)

{

if (e.NavigationMode == NavigationMode.New)

{

var extendedSession = new ExtendedExecutionSession();

extendedSession.Reason = ExtendedExecutionReason.LocationTracking;

extendedSession.Description = "Location tracking";

 

ExtendedExecutionResult result = await extendedSession.RequestExtensionAsync();

if (result == ExtendedExecutionResult.Allowed)

{

Debug.WriteLine("Background execution approved");

}

else

{

Debug.WriteLine("Background execution denied");

}

}

}

La prima riga di codice ci assicura che l'inizializzazione sia fatta solo quando la modalità di navigazione è New, ovvero si tratta del primo caricamento della pagina: questo per evitare che, ad esempio, l'inizializzazione venga ripetuta ogni volta che l'utente, da una pagina secondaria, torni indietro a quella principale.

Dopodiché, il codice è praticamente identico a quello visto in precedenza. Le uniche differenze sono:

  • L'utilizzo del valore LocationTracking per la proprietà Reason della classe ExtendedExecutionSession
  • Anche in questo caso, è importante verificare se il sistema operativo ci abbia autorizzato a continuare l'esecuzione in background: in realtà, però, in caso affermativo non dobbiamo fare nulla di particolare, dato che l'applicazione continuerà semplicemente le sue attività in background, come se non fosse mai stata sospesa. Di conseguenza, possiamo sfruttare il risultato dell'operazione RequestExtensionAsync() per scopi di logging e per dare un avviso all'utente, ma non dobbiamo eseguire alcuna operazione particolare. Attenzione: se vi dimenticate di abilitare la capability Location nel file di manifest, la chiamata a questo metodo vi ritornerà sempre Denied.

E' importante sottolineare come, anche se l'applicazione sia effettivamente in esecuzione, non abbia comunque il controllo della UI: se avete bisogno di mostrare degli avvisi di tipo visivo all'utente, perciò, dovrete sfruttare i meccanismi predisposti per questo scenario, come le notifiche.

Ovviamente, dato che questa modalità lascia maggiore libertà, ha anche dei vincoli più stringenti rispetto allo scenario di semplice salvataggio dei dati. Nello specifico, è possibile avere solamente una applicazione in esecuzione sul dispositivo in questo stato; se l'utente avviasse un'altra applicazione che fa uso dell'esecuzione in background, la nostra verrebbe terminata per lasciare spazio a quella nuova.

Inoltre, anche in questo caso il sistema operativo ha la possibilità di valutare, in base allo stato delle risorse di sistema, se negare l'esecuzione in background o se terminarla prematuramente.

In conclusione

Le funzionalità di esecuzione in background descritte in queste post saranno molto apprezzate dagli sviluppatori, in quanto aprono una serie di nuovi scenari che non era possibile gestire in Windows 8.1. In conclusione, vi ricordo che la classe ExtendedExecutionSession fa parte della Universal Windows Platform di Windows 10 e, di conseguenza, anche se risulta sicuramente più utile in scenari mobile, è utilizzabile anche da applicazioni desktop, IoT, ecc.

Happy coding!