http://blogs.infosupport.com/blogs/alexb/archive/2011/05/26/multi-tasking-in-windows-phone-7-1.aspx 原帖地址
One of the coolest features in Windows Phone 7.1 is the new multi tasking feature (next to the new LINQ to SQL support of course). In this article I’ll show you how this works.
Background
It is important to understand how multi tasking in Windows Phone 7.1 is implemented. I first thought that I could have some kind of background service running, comparable to a background service in Windows. Well, this isn’t the case. Multi tasking in Windows Phone 7.1 is so called “Scheduled Multi Tasking”. This means that I can define a piece of code (we’ll get to this later) and the operating system is then responsible for scheduling this piece of code. How often and when this piece of code is run is dependant on multiple factors:
- The kind of code to run.
- The state of the device.
- Available resources.
- And so on.
Depending on the factors outlined above, Windows Phone 7.1 will run your piece of code or not. This means for certain pieces of code, that they may never be run. Windows Phone 7.1 supports two kinds of code that you can schedule to run in the background, these two kinds of code are represented by the following classes:
- PeriodicTask
- ResourceIntensiveTask
Depending on the type of task you choose, the schedule and the constraints that the tasks must adhere to, differ. These are the constraints for a PeriodicTask:
- MemoryCap: 5MB.
- Scheduled Interval: 30 minutes, but the execution time can drift by 10 minutes.
- Scheduled Duration: 15 seconds.
- Battery Save Mode can prevent execution.
- There is a limit per device how many PeriodicTasks can be scheduled. This can be as low as six.
These are the constraints for a so called ResourceIntensiveTask:
- MemoryCap: 5MB.
- Scheduled Interval: Uncertain: A ResourceIntensiveTask will run when the phone meets the following conditions:
- It’s connected to an external power source.
- The battery is charged over 90%.
- It has a WIFI connection or it’s connected through a PC.
- The device screen is locked.
- There is no active phone call.
- Scheduled Duration: 10 minutes (If a RecourseInsensiveTask is run and the conditions of the devices change in a way that the ResourceIntensiveTask shouldn't have been run, it’s terminated immediately).
You can see by looking at the constraints above that there is no guarantee that a ResourceIntensiveTask will be run at all. The idea is that you use the PeriodicTask to do small updates, refresh an RSS feed for example. A typical scenario for a ResourceIntensiveTask would be an occasionally connected application, which stores data locally (perhaps in the new local database feature). If all the above conditions are met, the data could be synchronized with the server.
Next to the constraints above, there are a couple of common constraints:
- Each scheduled task will stop being scheduled after two weeks from the time it was first scheduled by using the Add() (later) method. Every time your main application is run, it can change this date to two weeks from the current time.
- Scheduled tasks can’t use the API’s listed on the following page: http://msdn.microsoft.com/en-us/library/hh202962(v=VS.92).aspx.
- Each application can schedule at most one PeriodicTask AND one ResourceIntensiveTask.
- Tasks can only be scheduled the first time after a user initiated action from your main application. So you can’t make an application which schedules a background task as soon as it is installed.
Scheduling a task
Let’s take a look at the code needed to schedule a background task. The sample application contains one button. When this button is clicked it will schedule or prolong the already scheduled task. Here is the code behind:
1: using System;
2: using System.IO.IsolatedStorage;
3: using System.Windows;
4: using Microsoft.Phone.Controls;
5: using Microsoft.Phone.Scheduler;
6:
7: namespace MainApplication
8: {
9: public partial class MainPage : PhoneApplicationPage
10: {
11: public MainPage()
12: {
13: InitializeComponent();
14: }
15:
16: protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
17: {
18: base.OnNavigatedTo(e);
19: string minutes;
20: if (NavigationContext.QueryString.TryGetValue("minutes", out minutes))
21: {
22: MessageBox.Show("The application was started by clicking a toast: " + minutes);
23: }
24: }
25:
26: private void HandleButtonClick(object sender, RoutedEventArgs e)
27: {
28: PeriodicTask myFirstTask;
29: myFirstTask = (PeriodicTask)ScheduledActionService.Find("MyFirstTask");
30: if (myFirstTask == null)
31: {
32: //The first time this application is run....
33: myFirstTask = new PeriodicTask("MyFirstTask");
34: myFirstTask.Description = "Updates the tile with the current minutes and shows a toast";
35: }
36: else
37: {
38: if (!myFirstTask.IsScheduled)
39: {
40: string lastResult;
41: if (IsolatedStorageSettings.ApplicationSettings.TryGetValue<string>("taskResult", out lastResult))
42: {
43: if (lastResult == "Succes")
44: {
45: //User must have disabled it.
46: MessageBox.Show("Please enable the background task in the settings menu and run this app again");
47: ScheduledActionService.Remove(myFirstTask.Name);
48: return;
49: }
50: else if (lastResult == "Faillure")
51: {
52: //Inspect isolated storage for more information and take appropriate action
53: }
54: }
55: }
56: ScheduledActionService.Remove(myFirstTask.Name);
57: }
58:
59: //Prolong the task or schedule a new one:
60: myFirstTask.ExpirationTime = DateTime.Now.AddDays(14);
61: try
62: {
63: ScheduledActionService.Add(myFirstTask);
64: }
65: catch (InvalidOperationException)
66: {
67: MessageBox.Show("Maximum amount of background tasks for your device might have been reached." +
68: " Disable some tasks or enable this task on the settings menu and run this app again.");
69: }
70: catch (SchedulerServiceException)
71: {
72: MessageBox.Show("Something really bad happened. an unexpected error has occurred.");
73: }
74:
75: }
76: }
77:
78: }
The most interesting part begins on line 29. When the button is clicked I first check whether the task already is scheduled. This is necessary because an application can only schedule one PeriodicTask. On line 33, if the task doesn’t exist, I create it. Setting the description on line 34 is mandatory for a PeriodicTask. This is the description the user sees when he views all the scheduled background tasks through the settings menu. If it does exist, I check whether the task is still being scheduled on line 38. A scheduled task can become unscheduled because of two reasons:
- The task called the Abort() method, to indicate something went wrong.
- The user disabled the background task through the settings menu. If this is the case, the user can only allow the application to restart it’s tasks when the application is launched. The user can’t enable a disabled background task from the settings menu.
As we’ll see later, the only way you can find out what has happened is through IsolatedStorage. My task writes a result to IsolatedStorage (“Success” or “Faillure”). If the last result is “Success”" and the task isn’t scheduled, it must mean that the user has disabled it. If this is the case, I notify the user and remove the task on line 47 so that it can be restarted. I would really have liked a property on the PeriodicTask class, which I can use to determine the reason a task isn’t scheduled anymore. Next to that, a way to find out if the task has been enabled again by the user, is also very welcome.
If the last result is “Faillure” or the task is still scheduled, I remove the task on line 56 (more about why I remove the task while it’s still scheduled in a moment). You can of course log error details to IsolatedStorage from your task and inspect them here. After the inspection has been done, I let the method continue. The code from line 60 will be executed whether a new task has been created or an existing task has been found (which has or hasn’t been aborted). On line 60 I set the ExpirationTime to two weeks from now, this is the maximum. This also means that an existing task is prolonged. You can’t just change the ExpirationTime for an already scheduled task, this is why I removed it on 56. Finally, on line 63 I submit my task to the ScheduledActionService through the add method. This will make sure that the task is scheduled. You have to catch the InvalidOperationException on line 65. This will occur when the maximum amount of background tasks has been reached for a device or when the user disabled the background task for this application. The SchedulerServiceException on line 70 must also be caught, as this means a serious internal error.
Note that to create a ResourceIntensiveTask, I only have to replace “PeriodicTask” in the code above with “ResourceIntensiveTask”. The configuration of the task is completely the same.
The SchedulerAgent
By now you, the well respected reader must have noticed something obvious…… I still haven’t shown you the code that actually get’s executed when the task is run by the operating system. This is because that code is is contained in a whole different Visual Studio project. You add this project by using the new “Windows Phone Task Scheduler Agent” project template:
You should add this project to the same solution as the main project and immediately add a reference from the main project to this project. After you created this project, it already contains one class. This class will contain the code that will be run by the operating system:
1: using System;
2: using System.IO.IsolatedStorage;
3: using System.Linq;
4: using Microsoft.Phone.Scheduler;
5: using Microsoft.Phone.Shell;
6:
7: namespace MyFirstAgent
8: {
9: public class TaskScheduler : ScheduledTaskAgent
10: {
11:
12: /// <summary>
13: /// Agent that runs a scheduled task
14: /// </summary>
15: /// <param name="task">
16: /// The invoked task
17: /// </param>
18: /// <remarks>
19: /// This method is called when a periodic or resource intensive task is invoked
20: /// </remarks>
21: protected override void OnInvoke(ScheduledTask task)
22: {
23:
24: try
25: {
26: ShellTile mainTile = ShellTile.ActiveTiles.First();
27: //Tile has been pinned. First tile is always the main tile
28: StandardTileData data = new StandardTileData();
29: data.Count = DateTime.Now.Minute;
30: data.Title = "Periodic";
31: data.BackTitle = "Periodic back";
32: mainTile.Update(data);
33:
34: ShellToast toast = new ShellToast();
35: toast.Title = "Info";
36: toast.Content = "Tile was modified! Tap here to open the app.";
37: toast.NavigationUri = new Uri("/MainPage.xaml?minutes=" + data.Count, UriKind.Relative);
38: toast.Show();
39:
40: IsolatedStorageSettings.ApplicationSettings["taskResult"] = "Succes";
41: NotifyComplete();
42: }
43: catch (Exception)
44: {
45: //Unschedule this task....
46: Abort();
47: IsolatedStorageSettings.ApplicationSettings["taskResult"] = "Faillure";
48: }
49: finally
50: {
51: IsolatedStorageSettings.ApplicationSettings.Save();
52: }
53: }
54:
55: /// <summary>
56: /// Called when the agent request is getting cancelled
57: /// </summary>
58: protected override void OnCancel()
59: {
60: base.OnCancel();
61:
62:
63: }
64: }
65: }
The most interesting method is the one on line 21. It’s the OnInvoke method. This method is called by the OS every time your task is run. The only argument is a ScheduledTask. This argument contains the PeriodicTask object I scheduled in the main application. You really only need this argument if your application schedules both a PeriodicTask and a ResourceIntensiveTask, since then you’ll need an “if(task is PeriodicTask)” statement to determine what code should be executed. In this method I use a couple of new API’s in Windows Phone 7.1:
- Line 26: I retrieve the main application tile. The first one is always the main tile, even if the application is not pinned, you can still update it and the user will see the update as soon as he pins the application.
- Line 28: I update the BackTitle. This also new in Windows Phone 7.1. Tiles have a back, and as soon as you update it, Windows Phone will flip the tile in the home screen periodically to show the back.
- On line 32: I update the tile with new information.
- Line 34: I create a toast object. This is a notification that is shown to the user.
- Line 37: A toast can have an Uri. When the user taps the toast, your application is started and your application navigates to this Uri, including the query string parameters. This way you can open your application in the correct state. You can see on line 16 of the main application that I do actually use this information in the OnNavigatedTo method. This is hard in the emulator to test, since the background task is run as soon as it’s added to the ScheduledActionService. When it’s run, my main application is still active and toast’s won’t be shown. So you’ll have to close the application and wait for 30 minutes. After 30 minutes the PeriodicTask will be run again.
- Line 43: I’ve wrapped all code in a try..catch block. If an Exception occurs I call the Abort method. This let’s Windows Phone know that the task should not be run again until I reschedule it from my main application. If everything went well, I call the NotifyComplete method on line 41. This let’s Windows Phone know that the task can be safely run again.
Conclusion
Wow! Multi tasking in Windows Phone is a real cool feature to have. The ones explained above aren’t the only ways to do some kind of multi tasking. In Windows Phone 7.1 there are also:
- Scheduled Tile Updates.
- Background Transfers.
- Alarms.
- Reminders.
And they all are really not that difficult to understand and program. Working in the emulator to test a PeriodicTask is a bit of a pain. As soon as I use the Add method to schedule a task, Visual Studio loses the debug connection, because the debugger attaches itself to the background task. Next to that, the scheduled task is invoked immediately while my application is still active, so you won’t see any toasts. To see toasts, you’ll have to wait thirty minutes for the PeriodicTask to be run again. It would be nice for the future that we can configure the interval in which PeriodicTasks are run in the emulator. This would simplify testing. Just for completeness, here is a screenshot which shows the background task in the settings menu, under “Applications” in Windows Phone 7.1:
You can see the description coming from my code in the last sentence in the screenshot above. The coming weeks I will be experimenting a lot more with Windows Phone 7.1, so if you find this interesting, keep an eye on this blog! You can find a working example here.