Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Consumare un background task

Link copiato negli appunti

Nell'introduzione ai background task abbiamo visto come creare un semplice task tramite un componente Windows Runtime (conosciuto anche come WinMD) e come associare l'esecuzione del task al verificarsi di un determinato evento (trigger) e a una o più condizioni. In questa lezione ci spingeremo più nei dettagli, per vedere come monitorare l'esecuzione di un background task, comprendere le limitazioni che il sistema impone, come annullare l'esecuzione di un task e come creare nuove versioni di task in esecuzioni.

Monitorare l'esecuzione di un background task

Se un'applicazione ha bisogno di conoscere il risultato dell'esecuzione di un background task, è possibile sfruttare l'evento Completed esposto dalla classe BackgroundTaskRegistration che, come abbiamo già accennato nella lezione precedente, rappresenta il task corrente una volta registrato presso il sistema operativo tramite il metodo Register.

Il listato seguente riprende quello già utilizzato nella scorsa lezione, aggiungendo l'evento Completed per segnalare all'utente il completamento del task.

var taskName = "bikePositionUpdate";
var taskEntryPoint = " Demo.Html.it.BackgroundTask.CS.BikeGPSPositionUpdateBackgroundTask";
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 = taskEntryPoint;
	var trigger = new MaintenanceTrigger(20, true);
	builder.SetTrigger(trigger);
	BackgroundTaskRegistration registration = builder.Register();
	registration.Completed += registration_Completed;
}

La relativa callback riceverà come parametri l'istanza di tipo BackgroundTaskRegistration, assieme ai relativi args di tipo BackgroundTaskCompletedEventArgs. A questo punto, l'handler notificherà all'utente il completamento del task tramite un semplice MessageDialog .

private void registration_Completed(BackgroundTaskRegistration sender, BackgroundTaskCompletedEventArgs args)
{
	var dialog = new MessageDialog("Task completato!",
	String.Format("Messaggio da: {0}", sender.Name));
	dialog.ShowAsync();
}

È importante sottolineare che, se il background task è stato eseguito (e completato) durante lo stato di sospensione dell'app o dopo che l'app stessa è stata terminata, l'handler dell'evento Completed sarà invocato solo dopo che l'applicazione è stata riattivata dal sistema operativo o lanciata nuovamente dall'utente. Se invece il task si è concluso quando l'app era ancora in foreground, la callback sarà immediatamente invocata.

Sapere che un task è stato portato a termine non è sempre sufficiente. Molto spesso avremo bisogno di sapere anche come si è concluso.

Per controllare se si sono verificati errori durante l'esecuzione del task, la classe BackgroundTaskCompletedEventArgs espone anche un utile metodo denominato CheckResult, il quale solleva un'eccezione nel caso in cui qualcosa è andata storta durante l'esecuzione del task.

Il prossimo listato mostra un semplice esempio dell'uso di questo metodo

private void registration_Completed(BackgroundTaskRegistration sender, BackgroundTaskCompletedEventArgs args)
{
	try
	{
		args.CheckResult();
		var dialog = new MessageDialog("Task completato con successo!",
		                               String.Format("Messaggio da: {0}", sender.Name));
		dialog.ShowAsync();
	}
	catch (Exception ex)
	{
		var dialog = new MessageDialog(String.Format("Errore: {0}", ex.Message),
		String.Format("Messaggio da: {0}", sender.Name));
		dialog.ShowAsync();
	}
}

Un altro utile evento esposto dalla classe BackgroundTaskRegistration è l'evento Progress che, come il nome suggerisce, permette di seguire l'avanzamento di un'attività. Nel relativo event handler, infatti, possiamo aggiornare la user interface, o aggiornare il tile o il badge dell'applicazione, indicando lo stato di avanzamento del task (ad esempio, specificando la percentuale di completamento delle relative attività).

Il seguente codice mostra come sfruttare questo evento per aggiornare periodicamente il tile applicativo con lo stato di avanzamento.

void registration_Progress(BackgroundTaskRegistration sender, BackgroundTaskProgressEventArgs args)
{
	string tileXmlString = ""
	+ "<visual>"
	+ "<binding template='TileWideText03'>"
		+ "<text id='1'>" + args.Progress.ToString() + "</text>"
	+ "</binding>"
	+ "<binding template='TileSquareText04'>"
		+ "<text id='1'>" + args.Progress.ToString() + "</text>"
	+ "</binding>"
	+ "</visual>"
	+ "</tile>";
	var tileXml = new Windows.Data.Xml.Dom.XmlDocument();
	tileXml.LoadXml(tileXmlString);
	var tile = new Windows.UI.Notifications.TileNotification(tileXml);
	Windows.UI.Notifications.TileUpdateManager.CreateTileUpdaterForApplication().Update(tile);
}

Per prima cosa, creiamo il documento XML per il tile, indicando la percentuale di avanzamento del task tramite la proprietà Progress esposta dall'oggetto BackgroundTaskProgressEventArgs ricevuto come parametro dall'handler dell'evento. Quindi utilizziamo il metodo CreateTileUpdaterForApplication della classe TileUpdateManager per aggiornare il tile.

Il valore che rappresenta lo stato di completamento di un task, espresso tramite uno short, può essere assegnato all'interno del metodo Run tramite la proprietà Progress dell'istanza di tipo IBackgroundTaskInstance che rappresenta il task corrente (e, come abbiamo visto nell'articolo introduttivo, è ricevuta come parametro dal metodo Run ).

Il seguente snippet mostra un esempio di uso della proprietà Progress per segnalare l'avanzamento del task:

public sealed class BikeGPSPositionUpdateBackgroundTask : IBackgroundTask
{
	public void Run(IBackgroundTaskInstance taskInstance)
	{
		// inizio attività
		taskInstance.Progress = 0;
		// prima attività
		taskInstance.Progress = 25;
		// seconda attività
		taskInstance.Progress = 50;
		// ecc...
	}
}

Comprendere le limitazioni di un task

Come abbiamo già accennato nell'articolo introduttivo, un background task deve essere necessariamente "leggero" in termini di risorse impiegate. WinRT impone infatti una serie di limiti piuttosto severi all'uso di risorse da parte di un task.

Ad esempio, nel caso di applicazioni che non sono nel Lock screen, la CPU è limitata a un secondo e un task può essere eseguito solo a intervalli di due ore, mentre per applicazioni aggiunte al Lock screen l'intervallo è di 15 minuti (in caso di task basati su MaintenanceTrigger. L'intervallo minimo è di 15 minuti anche se l'app non è stata aggiunta al Lock screen; in questo caso, tuttavia, il device deve essere alimentato a corrente e non a batteria). Inoltre, se il device è alimentato a batterie, il task incontra dei limiti anche nell'uso della rete, in base allo specifico hardware monetato sul device.

Ad esempio, con una scheda di rete da un 10Mbps, un'app che si trova sul Lock screen può consumare circa 450 MB al giorno, mentre un'app non sul inserita nel Lock screen può consumare 75 MB al giorno (per maggiori dettagli sulle limitazioni relative ai background task si rinvia alla documentazione su MSDN).

Per evitare che questi limiti interferiscano con applicazioni pensate per comunicazioni in real-time, i task basati su ControlChannelTrigger o PushNotificationTrigger ricevono una quota di risorse garantite (sia in termini di CPU che di rete). Queste quote garantite sono indipendenti dalle caratteristiche hardware del device. In altre parole, WinRT considera questi particolari task come "critici", e automaticamente alloca le risorse necessarie alla loro esecuzione.

Se un task eccede i limiti previsti, viene sospeso da WinRT. Puoi controllare se e quante volte un task è stato sospeso ispezionando la proprietà SuspendedCount del task corrente all'interno del metodo Run, ad esempio per interrompere l'esecuzione del task dopo un certo numero di sospensioni, come mostrato nel prossimo snippet:

public sealed class BikeGPSPositionUpdateBackgroundTask : IBackgroundTask
{
	public void Run(IBackgroundTaskInstance taskInstance)
	{
		// Controlla quante volte il task è stato sospeso
		// ed eventualmente ne interrompe l'esecuzione
		if (taskInstance.SuspendedCount > 5)
			return;
	}
}

Annullare un task in esecuzione

Quando un task è in esecuzione, normalmente non può essere interrotto. Tuttavia, se il sistema rileva che l'attività in background è inattiva o bloccata, il task verrà terminato. Per poter reagire a questa eventualità, l'interfaccia IBackgroundTaskInstance espone uno specifico evento denominato Canceled che viene sollevato quando vi è una richiesta di cancellazione del task.

Il luogo in cui controllare se è stata richiesta la cancellazione del task è rappresentato dal metodo Run. Il modo più semplice di farlo consiste nel dichiarare una variabile booleana a livello di classe e impostarla a true nell'handler dell'evento Canceled (La parola chiave volatile in corrispondenza della variabile indica che questa potrebbe essere modificata da più thread in esecuzione contemporaneamente). Il seguente snippet mostra come controllare l'eventuale richiesta di cancellazione.

public sealed class BikeGPSPositionUpdateBackgroundTask : IBackgroundTask
{
	volatile Boolean _cancelRequested = false;
	public void Run(IBackgroundTaskInstance taskInstance)
	{
		taskInstance.Canceled += newBackgroundTaskCanceledEventHandler(OnCanceled);
		// Aggiorna le coordinate GPS
		// Aggiorna lo stato di avanzamento
		taskInstance.Progress = 10;
		if (_cancelRequested == true)
			return;
		// Seconda operazione
		taskInstance.Progress = 20;
		/// ...
	}
	private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
	{
		// possiamo usare la proprietà sender.Task.Name per identificare il task
		this._cancelRequested = true;
	}
}

Nel metodo Run , la prima linea di codice si abbona all'evento Canceled . Quindi procede con l'esecuzione del task, controllando la variabile booleana e, nel caso in cui vi sia una richiesta di cancellazione, interrompe l'esecuzione del codice.

Nel caso in cui l'app abbia bisogno di conoscere se il task è stato completato oppure cancellato, possiamo utilizzare lo storage per salvare lo stato del task e successivamente rileggerlo dall'applicazione nell'handler dell'evento Completed relativo al task corrente. Il prossimo snippet mostra questo punto.

public sealed class BikeGPSPositionUpdateBackgroundTask : IBackgroundTask
{
	volatile Boolean _cancelRequested = false;
	public void Run(IBackgroundTaskInstance taskInstance)
	{
		taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);
		// Aggiorna le coordinate GPS
		// Aggiorna lo stato di avanzamento
		taskInstance.Progress = 10;
		if (_cancelRequested == true)
		{
			Windows.Storage.ApplicationData.Current.LocalSettings.Values["status"] = "canceled";
			return;
		}
		// Seconda operazione
		taskInstance.Progress = 20;
		/// ....
		Windows.Storage.ApplicationData.Current.LocalSettings.Values["status"] = "completed";
	}
	private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
	{
		// puoi usare la proprietà sender.Task.Name per identificare il task
		this._cancelRequested = true;
	}
}

Prima di "fermare" il codice nel metodo Run , il codice imposta il valore della chiave "status" a canceled nel LocalSettings, ovvero nello storage persistente dedicato all'applicazione. Se il task completa il suo lavoro, al termine del metodo il codice imposta il valore della chiave a completed .

Il codice seguente ispeziona il valore nei LocalSettings per determinare se il task ha completato il suo lavoro:

private void registration_Completed(BackgroundTaskRegistration sender, BackgroundTaskCompletedEventArgs args)
{
	try
	{
		args.CheckResult();
		var status = Windows.Storage.ApplicationData.Current.LocalSettings.Values["status"];
		MessageDialog dialog;
		if (status == "canceled")
			dialog = new MessageDialog("Task annullato");
		else
			dialog = new MessageDialog("Task completato");
		dialog.ShowAsync();
	}
	catch (Exception ex)
	{
		var dialog = new MessageDialog("Task in Errore " + ex.Message);
		dialog.ShowAsync();
	}
}

Il background task persiste le informazioni nel file system locale indipendentemente dalla sua versione.

Aggiornare un background task

Un aggiornamento dell'applicazione dal Windows Store non modifica gli eventuali background task che l'applicazione ha lanciato. In pratica i task non vengono modificati dall'aggiornamento di una applicazione e vivono, come abbiamo avuto modo di spiegare nel primo articolo, in "un mondo parallelo".

Se è necessario aggiornare il codice di un background task, occorre creare una nuova versione dell'applicazione che registra un background task tramite il trigger ServiceComplete: in questo modo l'applicazione viene "avvertita" quando viene aggiornata e può deregistrare le vecchie versioni dei task e lanciare le nuove.

Il listato seguente ricerca e deregistra un task al suo avvio.

Il listato seguente ricerca e deregistra un task al suo avvio.

namespace Demo.Html.it.BackgroundTask.CS
{
	public sealed class ServicingCompleteBackgroundTask : IBackgroundTask
	{
		public void Run(IBackgroundTaskInstance taskInstance)
		{
			// Look for Task v1
			var task = FindTask("bikePositionUpdate");
			if (task != null)
			{
				task.Unregister(true);
			}
			var builder = new BackgroundTaskBuilder();
			builder.Name = "bikePositionUpdate";
			builder.TaskEntryPoint = "Demo.Html.it.BackgroundTask.CS.BikeGPSPositionUpdateBackgroundTask";
			builder.SetTrigger(new SystemTrigger(SystemTriggerType.TimeZoneChange, false));
			builder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable));
			BackgroundTaskRegistration taskRegistration = builder.Register();
		}
		public static BackgroundTaskRegistration FindTask(string taskName)
		{
			foreach (var task in BackgroundTaskRegistration.AllTasks)
			{
				if (task.Value.Name == taskName)
				{
					return (BackgroundTaskRegistration)(task.Value);
				}
			}
			return null;
		}
	}
}

Il parametro del metodo Unregister impostato a true forza la cancellazione del task, se implementato per il background task. Il metodo FindTask è semplicemente un helper che potete usare nel vostro codice per ricercare i task registrati sul sistema per assegnato nel task in fase di registrazione e restituirlo al chiamante.

L'ultima coda da fare è usare un task di tipo ServiceComplete nel codice applicativo per registrare questo task di sistema che aggiornare il task esistente (ricordatevi anche di aggiornare l'application manifest per includere il nuovo task di "servizio"):

var trigger = new SystemTrigger(SystemTriggerType.ServicingComplete, false);
builder.SetTrigger(trigger);

Debug di un background task

Debuggare un background task è praticamente impossibile se seguite l'approccio classico in quanto il task parte in background ed è slegato dall'applicazione stessa. Prima di fare un salto nel passato e adottare metodi di tracing custom, sappiate che Visual Studio 2012 e Visual Studio 2013 mettono a disposizione un debugger integrato che consente di attivare un task "a comando" ed eseguire il debug del relativo codice.

Ricordatevi che un task basato su timer (TimeTrigger ) o su un trigger di manutenzione (MaintenanceTrigger ) può essere eseguito nei 15 minuti successivi alla registrazione in base al timer interno del Windows Runtime: per questo motivo, debuggare il codice manualmente sarebbe una operazioni time-consuming.

Per usare il debugger integrato, occorre assicurarsi di referenziare il background task nel progetto e che quest'ultimo sia configurato come "Windows Runtime Component". Inoltre il progetto deve dichiarare il background task nel manifest dell'applicazione.

Non resta che inserire un breakpoint nel metodo Run o usare la classe Debug per inserire le informazioni di debug nella finestra di output.

È necessario però avviare il progetto almeno una volta per registrare il task nel sistema prima di poter usare la toolbar Debug Location per attivare il task in background. La toolbar infatti può mostrare solo i task già registrati nel sistema e in attesa di trigger. Se la toolbar non è visibile, è possibile attivarla tramite il menu View > Toolbars.

La figura seguente mostra la toolbar dove è presente il background task denominato bikePositionUpdater.

\2

Ovviamente avviando il task, il debugger si fermerà sul breakpoint impostato.

Regole per l'uso ottimale dei task

Prima di chiudere l'articolo e passare al successivo dove approfondiremo il trasferimento dei dati in background e vedremo come tenere aperto un canale di comunicazione con un servizio remoto, è importante sottolineare alcune regole per un uso ottimale dei task. Le regole principali sono le seguenti:

  • Non eccedere i limiti di CPU e rete. I task devono essere leggeri per evitare di consumare batteria e fornire una user experience migliore per le applicazioni in foreground.
  • L'applicazione deve utilizzare gli handler di completamento e progressione per informare l'utente sullo stato delle attività in background. Inoltre deve implementare la possibilità di annuallare un task in esecuzione.
  • Se il metodo Run usa codice asincrono, assicurarsi di usare correttamente i deferral per evitare che il codice venga terminato prima del suo completamento
  • Dichiarare ogni task nell'application manifest e ogni trigger associate con esso. Altrimenti l'applicazione non sarà in grado di registrare i task a runtime.
  • Implementare il trigger ServiceComplete per prepararsi a successivi aggiornamenti.
  • Usare tile e badge per fornire all'utente informazioni dal task in esecuzione.
  • Usare lo storage ApplicationData per condividere dati fra il task in background e l'applicazione.

Nel prossimo articolo ci divertiremo con trasferimento dei dati in background e applicazioni che necessitano di mantenere un canale di comunicazione aperto con servizi remoti.

Ti consigliamo anche