To put this post into perspective see Windows Phone 7 App Development Helpers and Time Savers.
Background agents are a very important feature for WP7 apps. Agents allow you to run code in background while the app is not running – and also while it is running.
Some pitfalls you should bear in mind:
- Beware of misleading agent memory usage reported under the debugger.
- Create an appropriate VS solution structure to comply with unsupported APIs.
- Web request default timeouts might be too long for agent duration constraint.
- App/Agent communication must be synchronized.
- VB.NET agent template bug.
Misleading agent memory usage under the debugger
PeriodicAgents have memory limit of 6MB. Agent duration and memory constraints are not enforced when debugging. With the debugger attached DeviceStatus reports a way higher memory usage then without the debugger. For an empty PeriodicTask DeviceStatus.ApplicationPeakMemoryUsage reports 3.5 MB with the debugger and 1.8 MB without the debugger. To get your agents real memory usage write to IsolatedStorage or a Tile or show a Toast.
VS solution structure
Some APIs like Phone.Shell are unsupported in Agents. Because the submission process enforces this using static code analysis, your submission will not only fail when using an unsupported API, but also when you simply reference it. To avoid painful refactorings, you should plan your solution structure accordingly from the beginning on.
The solution structure of the demo application is optimized to adhere to the agent API and resource constraints. The projects are numbered to show the reference hierarchy.
VB.NET agent template bug
With VB.NET Visual Studio, Add Agent Project creates a Class ScheduledAgent with an invalid constructor. I wrongly names the constructor Sub ScheduledAgent(), correctly it should be Sub New(). This bug results in your error handler never being registered and might lead to your agent being disabled on the users phone without you ever knowing.
Web request default timeouts might be too long for agent duration constraint
PeriodicAgents are constraint to 25 sec duration. WebClient and HttpWebRequest seem to have a default timeout of approx. 20 sec. Because both do not support to set at timeout you should consider implementing a timeout manually. When doing so, don’t forget the ignore WebExceptions with WebExceptionStatus.RequestCanceled that might arrive after cancelling.
App/Agent communication must be synchronized
Microsoft’s recommendations for app/agent communications are very strict: For one-direction communication where the foreground application writes and the agent only reads, we recommend using an isolated storage file with a Mutex. … We recommend that you do not use IsolatedStorageSettings to communicate between processes because it is possible for the data to become corrupt“.
The following sample uses a Mutex for synchronization:
Public Class IsolatedStorageFile Public Shared SerializationFormatter As SerializationFormatter = SerializationFormatter.Binary 'Beware! XmlSerializer can not seriallize TimeSpan 'Public Shared SerializationFormatter As SerializationFormatter = Threading.SerializationFormatter.DataContract Private Const _timoutToPreventBlocking As Integer = 5 * 1000 Public Shared Sub WriteSynchronized(filePath As String, fileContent As Object) filePath = SetFileNameExtension(filePath) Dim mutex As New Mutex(False, filePath) Try If mutex.WaitOne(_timoutToPreventBlocking) Then Using isoFile = IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForApplication(), isoStream = isoFile.OpenFile(filePath, FileMode.Create) Select Case SerializationFormatter Case SerializationFormatter.Binary SilverlightSerializer.Serialize(fileContent, isoStream) Case SerializationFormatter.XML Dim serializer = New XmlSerializer(fileContent.GetType) serializer.Serialize(isoStream, fileContent) Case SerializationFormatter.DataContract Dim serializer = New DataContractSerializer(fileContent.GetType) serializer.WriteObject(isoStream, fileContent) End Select End Using Else 'This should never happen. The phone runtime even releases mutexes for aborted processes. But better safe than sorry! Dim ex As New ThreadStateException("Unable to acquire mutex for app/agent sync") Trace.Error(ex.Message) Throw ex End If Finally mutex.ReleaseMutex() End Try End Sub Public Shared Function ReadSynchronized(Of contentType)(filePath As String) As contentType ... End Function End Class