Mango brings a number of new features to support background processing; many of which I’ll be covering in coming days and weeks. The most general of all of these is the Background Agent.
Background Agents come in two flavors:
- PeriodicTask
- ResourceIntensiveTask
The PeriodicTask runs approximately every 30 minutes, for about 25 seconds. This is perfect for updating tiles, sending toast notifications or otherwise keeping the user updated as to the status of, e.g., an application that retrieves data from a web service.
The ResourceIntensiveTask, on the other hand, run when certain conditions are met (the phone is connected to wifi or a computer rather than the cellular network, the phone is plugged into A/C rather than running on battery, the battery must in any case be at least 90% charged, etc.) The advantage of the ResourceIntensiveTask is that it will run for approximately 10 minutes at a time, allowing for more, err, Resource Intensive tasks, such as synchronizing a local and a distributed database.
The actual setup and use of both types is very similar, as is made obvious by the Sample program on MSDN. This article strips that sample down to just the Periodic Task to make clear how easy it is to create and use a Background Agent.
The key to working with Background Agents is that you will create two projects in one solution:
- The Main (foreground) project – which will contain the UI
- A Task Agent project – with a class that derives from ScheduledTaskAgent
Fortunately, there is a Visual Studio template of type Windows Phone Scheduled Task Agent that creates just what we need for the second project.
To see this at work, begin by creating a Windows Phone project (using the Windows Phone Application template). Name it BackgroundAgents. Add a second project using the Windows Phone Scheduled Task Agent template. You can use the default name of ScheduledTaskAgent1.
Critically important is that you create a reference to the second project in the first project (click on the foreground project, then click Add Reference…. and use the Project tab to find and select the project).
The Scheduled Task Agent project has one class, ScheduledAgent, which has one important method, OnInvoke. This method is called whenever the Task is launched. It is also critically important that you call NotifyComplete() when this method completes its work successfully or Abort() if it cannot complete its work.
Here’s the code we’ll add to OnInvoke, which will create a toast message and display it periodically (approximately every 30 minutes unless you are debugging, in which case, approximately every 30 seconds).
protected override void OnInvoke( ScheduledTask task ) { string toastMessage = "Periodic task running."; ShellToast toast = new ShellToast(); toast.Title = "Background Agent Sample"; toast.Content = toastMessage; toast.Show(); #if DEBUG_AGENT ScheduledActionService.LaunchForTest( task.Name, TimeSpan.FromSeconds(30)); #endif NotifyComplete(); }
All that’s left to do is to fire off the task, which we do in the foreground application. The key bit of Xaml is the checkbox that is used to start and stop the task,
<CheckBox Name="PeriodicCheckBox" IsChecked="{Binding IsEnabled}" Checked="PeriodicCheckBox_Checked" Unchecked="PeriodicCheckBox_Unchecked" />
The checked and unchecked event handlers are in the code behind,
private void PeriodicCheckBox_Checked( object sender, RoutedEventArgs e ) { StartPeriodicAgent(); } private void PeriodicCheckBox_Unchecked( object sender, RoutedEventArgs e ) { RemoveAgent( periodicTaskName ); }
RemoveAgent is simpler, so let’s look at that first. YOu need only call Remove on the SecheduledActionService, passing in the name of the agent and catching any resulting exception (there is no action to take in the case of an exception except to not crash).
private void RemoveAgent( string name ) { try { ScheduledActionService.Remove( name ); } catch (Exception) { } }
The more interesting code is in StartPeriodicAgent; our method for firing up a PeriodicTask. This is done in two steps:
- Remove the task if it already exists
- Add the task (and handle the exception if background agents for this application are disabled)
private void StartPeriodicAgent() { AgentIsEnabled = true; // If this task already exists, remove it periodicTask = ScheduledActionService.Find( periodicTaskName ) as PeriodicTask; if (periodicTask != null) { RemoveAgent( periodicTaskName ); } periodicTask = new PeriodicTask( periodicTaskName ); periodicTask.Description = "This demonstrates a periodic task."; try { ScheduledActionService.Add( periodicTask ); PeriodicStackPanel.DataContext = periodicTask; #if(DEBUG_AGENT) ScheduledActionService.LaunchForTest( periodicTaskName, TimeSpan.FromSeconds( 20 ) ); #endif } catch (InvalidOperationException exception) { if (exception.Message.Contains( "BNS Error: The action is disabled" )) { MessageBox.Show( "Background agents for this application have been disabled by the user." ); AgentIsEnabled = false; PeriodicCheckBox.IsChecked = false; } } }
Finally, when we navigate to the page we test to see if this task already exists; if so we assign it as the datacontext for the display StackPanel,
protected override void OnNavigatedTo( System.Windows.Navigation.NavigationEventArgs e ) { periodicTask = ScheduledActionService.Find( periodicTaskName ) as PeriodicTask; if (periodicTask != null) { PeriodicStackPanel.DataContext = periodicTask; } }
For completeness, here is the entire Xaml from the ContentPanel:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <StackPanel> <StackPanel Orientation="Vertical" Name="PeriodicStackPanel" Margin="0,0,0,40"> <TextBlock Text="Periodic Agent" Style="{StaticResource PhoneTextTitle2Style}" /> <StackPanel Orientation="Horizontal"> <TextBlock Text="name: " Style="{StaticResource PhoneTextAccentStyle}" /> <TextBlock Text="{Binding Name}" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="is enabled" VerticalAlignment="Center" Style="{StaticResource PhoneTextAccentStyle}" /> <CheckBox Name="PeriodicCheckBox" IsChecked="{Binding IsEnabled}" Checked="PeriodicCheckBox_Checked" Unchecked="PeriodicCheckBox_Unchecked" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="is scheduled: " Style="{StaticResource PhoneTextAccentStyle}" /> <TextBlock Text="{Binding IsScheduled}" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="last scheduled time: " Style="{StaticResource PhoneTextAccentStyle}" /> <TextBlock Text="{Binding LastScheduledTime}" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="expiration time: " Style="{StaticResource PhoneTextAccentStyle}" /> <TextBlock Text="{Binding ExpirationTime}" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="last exit reason: " Style="{StaticResource PhoneTextAccentStyle}" /> <TextBlock Text="{Binding LastExitReason}" /> </StackPanel> </StackPanel> </StackPanel> </Grid>
See also this video.
5 Responses to Background Agents