Il ciclo di vita di un'applicazione Windows Store presenta notevoli differenze rispetto alle applicazioni "tradizionali". In primo luogo, solo un'applicazione alla volta (o al massimo due, in modalità "snapped") può essere in esecuzione in foreground, mentre eventuali altre app vengono poste in background dal sistema. In secondo luogo, WinRT può sospendere o addirittura terminare le applicazioni in background ogni volta che abbia la necessità di liberare risorse. Per questo motivo, se la nostra app deve svolgere un qualche tipo di attività in background (come ad esempio scaricare un file o aggiornare i tile), è necessario implementare un background task.
Un background task permette infatti ad un'app di svolgere operazioni in background, eventualmente in un processo diverso da quello principale dell'app, anche se quest'ultima si trova nello stato di sospensione. Dall'altro lato, però, un background task viene eseguito in un ambiente controllato dal Windows Runtime e, come vedremo fra breve, può accedere solo a una quantità limitata di risorse di sistema.
Per questo motivo, un background task dovrebbe limitarsi ad eseguire compiti non troppo complessi e che non richiedano l'azione dell'utente. Inoltre meglio evitare di usare un background task per eseguire logica di business o calcoli troppo complessi, viste la ridotta quantità di risorse di sistema a disposizione.
Per creare un background task, è necessario prima di tutto creare un nuovo progetto di tipo Windows Runtime Component (a questo argomento saranno dedicati appositi articoli di approfondimento all'interno di questa guida), come mostrato nella seguente immagine:
All'interno del progetto del componente, occorre definire una classe non ereditabile (sealed
) che implementi l'interfaccia IBackgroundTask
e registrarla presso il sistema operativo tramite la classe BackgroundTaskBuilder
.
L'implementazione dell'interfaccia IBackgroundTask
include un solo metodo dal nome piuttosto auto-esplicativo: Run. Immaginiamo di dover creare un background task che permetta di tracciare il percorso GPS seguito dall'utente anche quando l'app non è in esecuzione in foreground. Il seguente listato mostra un'implementazione di base dell'interfaccia IBackgroundTask
.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel.Background;
namespace Demo.Html.it.BackgroundTask.CS
{
public sealed class BikeGPSPositionUpdateBackgroundTask : IBackgroundTask
{
public void Run(IBackgroundTaskInstance taskInstance)
{
// Aggiornamento coordinate GPS
}
}
}
Adesso che abbiamo il nostro background task, occorre registrarlo tramite la classe BackgroundTaskBuilder
e associare l'evento che scatenerà l'esecuzione del task. Quando questo evento, definito trigger, verrà sollevato, il sistema operativo invocherà il metodo Run
. Il prossimo snippet mostra entrambi questi passaggi:
var builder = new BackgroundTaskBuilder();
builder.Name = "bikePositionUpdate";
builder.TaskEntryPoint = "Demo.Html.it.BackgroundTask.CS.BikeGPSPositionUpdateBackgroundTask";
builder.SetTrigger(/* tipo di trigger */);
BackgroundTaskRegistration taskRegistration = builder.Register();
Dopo aver definito una nuova istanza della classe BackgroundTaskBuilder
, il codice assegna un nome al background task e definisce l'entry point del task, rappresentato dalla classe che implementa l'interfaccia IBackgroundTask
, usando la sintassi namespace.nomedellaclasse
. La riga di codice successiva invoca il metodo SetTrigger
per associare al background task il trigger (ossia l'evento) che consentirà a WinRT di sapere quando avviare il background task. Quest'ultimo metodo accetta come unico parametro un oggetto che implementa l'interfaccia IBackgroundTrigger
. In seguito vedremo quali sono i trigger che WinRT mette a disposizione del nostro background task.
Infine, l'ultima riga di codice procede a registrare il background task presso il sistema operativo tramite il metodo Register
della classe BackgroundTaskBuilder
; questo metodo restituisce un'istanza di tipo BackgroundTaskRegistration
(avremo modo di tornare su questa classe più avanti).
Trigger e condizioni
WinRT mette a disposizione diverse tipologie di trigger che permettono di schedulare l'esecuzione di background task a fronte di specifici eventi. I trigger sono i seguenti:
- SystemTrigger
- MaintenanceTrigger
- TimeTrigger
- PushNotificationTrigger
- NetworkOperatorNotificationTrigger
- NetworkOperatorHotspotAuthenticationTrigger
Alcuni di questi, come SystemTrigger
e MaintenanceTrigger
hanno una portata più generale, mentre gli altri hanno un campo di applicazione più limitato e specifico.
SystemTrigger
In particolare, il trigger di tipo SystemTrigger viene sollevato a fronte di specifici eventi di sistema (come ad esempio il completamento dell'aggiornamento di un'app, il cambio di fuso orario, l'aggiunta o la rimozione dell'app dal Lock screen, etc.).
Il costruttore della classe SystemTrigger
accetta due parametri:
- il primo parametro, di tipo
SystemTriggerType
, rappresenta il tipo di trigger di sistema associate al background task, - mentre il secondo, denominato oneShot, indica a WinRT se avviare il task una volta soltanto (se impostato a
true
) ovvero ogni volta che l'evento viene sollevato.
La seguente riga di codice mostra come utilizzare la classe SystemTrigger
per definire un background task che verrà eseguito ogni volta che la connessione a internet torna a essere disponibile.
builder.SetTrigger(new SystemTrigger(SystemTriggerType.InternetAvailable, false));
L'enumerazione completa dell'enum SystemTriggerType
è la seguente:
Tipologia di trigger | Descrizione |
---|---|
Invalid |
non rappresenta un tipo di trigger valido. |
SmsReceived |
viene sollevato quando un nuovo SMS è ricevuto dal device mobile |
UserPresent |
viene sollevato quando l'utente torna presente, ossia quando sblocca il Lock screen. Perché questo trigger possa funzionare è necessario che l'app sia aggiunta al Lock screen |
UserAway |
viene sollevato quando l'utente è assente ed entra in funzione il Lock screen. Anche in questo caso è necessario che l'app sia aggiunta al Lock screen perché il background task possa essere effettivamente registrato |
NetworkStateChange |
viene sollevato quando si verifica un cambiamento nella connessione o nei relativi costi |
ControlChannelReset |
viene sollevato quando il control channel viene resettato. È necessario che l'app sia aggiunta al Lock screen perché il background task possa essere effettivamente registrato |
InternetAvailable |
viene sollevato quando la connessione a internet diviene disponibile |
SessionConnected |
viene sollevato quando la sessione è connessa. È necessario che l'app sia aggiunta al Lock screen perché il background task possa essere effettivamente registrato |
ServicingComplete |
viene sollevato quando il sistema operativo ha terminato l'aggiornamento di un'app |
LockScreenApplicationAdded |
viene sollevato quando un'app viene aggiunta al Lock screen |
LockScreenApplicationRemoved |
viene sollevato quando un'app viene rimossa dal Lock screen |
TimeZoneChange |
viene sollevato quando la "time zone" del device viene modificata e nel caso in cui la nuova zona preveda una modifica di fuso orario che comporti una modifica all'ora di sistema. |
OnlineIdConnectedStateChange |
viene sollevato quando l'account Windows Live viene modificato |
BackgroundWorkCostChange |
viene sollevato quando il costo dell'operazione in background si modifica.. È necessario aggiungere l'applicazione al Lock Screen affinchè questo evento possa essere registrato |
MaintenanceTrigger
L'altro trigger di carattere generale è quello di tipo MaintenanceTrigger, che può essere utilizzato per schedulare operazioni periodiche da eseguire in background. In questo senso, svolge funzioni analoghe al TimeTrigger
, con la differenza che quest'ultimo viene sollevato solo se l'utente ha aggiunto l'app al Lock screen. Un'altra importante differenza è che le attività in background che utilizzano un MaintenaceTrigger
vengono eseguite solo quando il device è collegato all'alimentazione (in caso contrario i relativi eventi non vengono sollevati).
Tanto il costruttore della classe MaintenanceTrigger
, tanto quello della classe TimeTrigger
accettano gli stessi due parametri:
- il primo, denominato
freshnessTime
, specifica il numero di minuti che devono trascorrere prima che il background task venga eseguito; - il secondo,
oneShot
, è unBoolean
che indica se il trigger deve essere sollevato solo una volta ovvero a intervalli di tempo regolari.
Ad esempio, tramite il seguente codice il background task verrà schedulato per essere eseguito ogni sessanta minuti:
builder.SetTrigger(new MaintenanceTrigger(60, false));
È importante ricordare che WinRT ha un timer interno che esegue i task schedulati ogni 15 minuti.
Questo significa che se freshnessTime
viene impostato a 15 minuti e oneShot
a false
, il task parte alla prossima occorrenza del timer interno (al massimo entro 15 minuti) e viene eseguito ogni 15 minuti. Se il task invece viene registrato con il parametro oneShot
a true
, il task viene eseguito una sola volta nei 15 minuti dal momento della registrazione.
Vuol dire anche che non è possibile impostare il freshnessTime
a un valore inferiore a 15 minuti. In caso contrario, otterremmo un'eccezione di tipo ArgumentException
, come mostrato nella prossima immagine:
Altri trigger
WinRT espone anche altri tipi di trigger specifici:
- il PushNotificationTrigger, che viene sollevato quando arriva una notifica tramite il Windows Push Notifications Service channel;
- il trigger NetworkOperatorNotificationTrigger, legato alla notifica di un operatore di rete mobile;
- il NetworkOperatorHotspotAuthenticationTrigger, che rappresenta un trigger di autenticazione dell'hotspot dell'operatore di rete mobile.
Le API di Windows 8.1 hanno infine aggiunto un ulteriore tipologia di trigger, rappresentata dalla classe LocationTrigger, che permette di eseguire background task in base alla posizione geografica dell'utente (geofencing).
Condizioni
È anche possibile definire condizioni che devono essere verificate dal sistema operativo prima di avviare un determinate background task. La classe BackgroundTaskBuilder
espone infatti il metodo AddCondition, il quale permette di aggiungere una singola condizione alla volta, sotto forma di un oggetto di tipo SystemCondition
.
builder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable));
Il costruttore della classe SystemCondition
accetta infatti una istanza di tipo SystemConditionType
, che enumera le possibili condizioni cui può essere subordinata l'esecuzione di un background task. L'enumerazione include le seguenti possibilità:
Condizione | Descrizione |
---|---|
Invalid |
Non è una condizione valida. |
UserPresent |
il background task può essere eseguito solo se l'utente è presente. Se il trigger viene sollevato durante l'assenza dell'utente, il relativo background task verrà eseguito non appena l'utente tornerà presente. |
UserNotPresent |
il background task può essere eseguito solo se l'utente non è presente. Se il trigger viene sollevato quando l'utente è presente, il relativo background task verrà eseguito non appena l'utente non sta più usando l'applicazione. |
InternetAvailable |
il background task viene eseguito solo se è presente l'accesso a internet. Se il trigger viene sollevato in assenza di una connessione a internet, il relativo background task verrà eseguito non la connessione tornerà disponibile. |
InternetNotAvailable |
il background task viene eseguito solo se manca l'accesso a internet. Se il trigger viene sollevato in presenza di una connessione internet, il relativo background task verrà eseguito non appena la connessione non sarà più disponibile. |
SessionConnected |
il background task viene eseguito solo se la sessione utente è connessa. Se il trigger viene sollevato quando l'utente non ha ancora effettuato il login, il relativo background task verrà eseguito non appena l'utente effettuerà l'accesso. |
SessionDisconnected |
il background task viene eseguito solo se l'utente ha effettuato il logout. Se il trigger viene sollevato quando l'utente è ancora loggato, il relativo background task verrà eseguito non appena l'utente effettuerà il logout. |
FreeNetworkAvailable |
il background task viene eseguito solo quando è disponibile una connessione di rete gratuita. |
BackgroundWorkCostNotHigh |
il background task verrà eseguito solo se il relativo carico di lavoro è basso. |
Il seguente snippet mostra come subordinare l'esecuzione di un background task al verificarsi di due condizioni:
builder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable));
builder.AddCondition(new SystemCondition(SystemConditionType.SessionConnected));
Dichiarare il background task nell'application manifest
Affinché la registrazione di un background task sia possibile, è necessario dichiarare la relativa capacità nell'application manifest, indicando anche il tipo di trigger che scatenerà l'evento. La prossima immagine mostra il manifest con la dichiarazione del background task e del relativo trigger.
Se aprite il manifest con l'editor XML, vedrete che Visual Studio ha aggiunto un'apposita sezione con l'indicazione dell'entry point del background task (nella solita forma namespace.nomedellaclasse
) e del tipo di task (systemEvent
).
<Extensions>
<Extension Category="windows.backgroundTasks" EntryPoint="Demo.Html.it.BackgroundTask.CS.BikeGPSPositionUpdateBackgroundTask">
<BackgroundTasks>
<Task Type="systemEvent" />
</BackgroundTasks>
</Extension>
</Extensions>
Tieni presente che se il tipo di trigger richiede l'interazione con il Lock screen (es. PushNotificationTrigger
, TimeTrigger
, ecc.), nell'application manifest dovrai anche specificare il modo con cui l'app mostrerà le notifiche sul Lock screen e fornire l'immagine da utilizzare per il badge.
Enumerare i task registrati
Quando si registra un background task, è importante essere sicuri di farlo una sola volta, altrimenti potresmmo vedere lo stesso task eseguito più volte. Per testare se un task è già stato registrato, è possibile utilizzare la classe BackgroundTaskRegistration
e verificare la proprietà Value.Name
come mostrato nel seguente snippet:
var taskName = "bikePositionUpdate";
var taskRegistered = false;
foreach (var task in BackgroundTaskRegistration.AllTasks)
{
if (task.Value.Name == taskName)
{
taskRegistered = true;
break;
}
}
if (!taskRegistered)
{
var builder = new BackgroundTaskBuilder();
builder.Name = taskName;
builder.TaskEntryPoint = "Demo.Html.it.BackgroundTask.CS.BikePositionUpdateBackgroundTask";
builder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable));
builder.AddCondition(new SystemCondition(SystemConditionType.SessionConnected));
TimeTrigger trigger = new TimeTrigger(10, true);
builder.SetTrigger(trigger);
BackgroundTaskRegistration taskRegistration = builder.Register();
}
Il codice esegue un foreach
sui background task registrati dall'applicazione sfruttando il relativo dictionary in sola lettura esposto tramite la proprietà AllTasks
della classe BackgroundTaskRegistration
. Quindi il codice procede a registrare il task corrente, ma solo nel caso in cui un task con lo stesso nome non sia stato già registrato presso il sistema operativo, evitando così di eseguire più volte lo stesso codice in background.
Eseguire codice asincrono in un background task
Se il codice da eseguire in background è asincrono, è necessario usare un deferral all'interno del metodo Run
, sfruttando il metodo GetDeferral
esposto dall'oggetto di tipo IBackgroundTaskInstance
ricevuto come parametro, come mostrato nel seguente codice:
public sealed class BikeGPSPositionUpdateBackgroundTask : IBackgroundTask
{
public async void Run(IBackgroundTaskInstance taskInstance)
{
var deferral = taskInstance.GetDeferral();
// codice asincrono con await
deferral.Complete();
}
}
Dal momento che si tratta di codice asincrono, utilizzeremo il pattern async/await per eseguire l'operazione asincrona (notate la parola chiave async
nella firma del metodo) e, al termine dell'operazione, marcheremo il deferral come completato tramite una chiamata al metodo Complete
. È importante ricordare di eseguire tutto il codice tra la richiesta del deferral e la chiamata al metodo Complete
, altrimenti il sistema assumerà che l'operazione è stata completata e che il thread può essere distrutto.
Nelle prossime lezioni scenderemo con esempi pratici in varie casistiche legate ai background task.