バックグラウンドでの生産性を上げる - バックグラウンド タスク
私の以前の記事 (バックグラウンドで生産性を上げる) では、Windows 8 のバックグラウンド モデルについてと、電源効率を高めるために画面に表示されていない場合でもアプリの生産性を上げる方法について説明しました。今回の記事では、バックグラウンド タスクについてと、アプリが中断されているときでもバックグラウンドでコードを実行する方法についてお話しします。また、独自のアプリ コードをバックグラウンドで実行する方法を示すサンプル コードを使って、2 つのよくあるシナリオ (ロック画面対応アプリで POP 電子メールを 15 分おきにダウンロードするシナリオと、デバイスが AC 電源に接続されているときにアプリがバックグラウンドで動作するシナリオ) について説明します。
バックグラウンド タスク トリガーは、多様なシナリオとアプリケーションに合わせて設計されているため、さまざまな要件やリソース管理の制約があります。常に最新の状態に保つ必要があるアプリ (電子メールや VOIP など) 向けに設計されたバックグラウンド タスク トリガーもあれば、もっと一時的なシナリオ (AC 接続時の管理タスクの実行や特定のシステム条件の変更時など) 向けに設計されたバックグラウンド タスク トリガーもあります。常に最新の状態に保つ必要があるアプリは、ロック画面に配置する必要がありますが、これは 7 つのアプリに制限されています。この点については、この記事の後方で説明します。対照的に、一時的な作業を行う場合、どのアプリでも AC 接続時に実行されるメンテナンス トリガーやシステム トリガーを使うことができます。これらのトリガーを使う場合、アプリをロック画面に配置する必要はありません。ロック画面に配置されたアプリは、頻繁に実行する必要があるため (数を制限して、ユーザーに制御権を与えたのはこのためです)、リソース管理の制約が緩くなっています。この点については、後で詳しくお話しします。
アプリの内容を最新の状態に保つだけの場合は、バックグラウンド タスクを使う必要はありません。「優れたタイル エクスペリエンスを開発する」という記事で説明したとおり、いつでもライブ タイルやスケジュール設定された通知を使うことができます。では、この点を踏まえてコードを見てみましょう。
以前の記事で説明したとおり、バックグラウンドで機能を実行するには独自のコードが必要な場合があります。たとえば、画像ライブラリ内のすべての写真をアプリ データベースに追加したり、それらの写真を何らかの方法で処理する (縮小版を生成するなど) とします。アプリがフォアグラウンドで実行されていて、ユーザーとやり取りしている際にこれを行ったり、デバイスが AC 電源に接続されているときのみバックグラウンドで実行されるメンテナンス トリガーによってバックグラウンド タスクを使うことができます。メンテナンス トリガーはどのアプリでも使うことができ、アプリをロック画面に配置する必要はありません。メンテナンス トリガーのバックグラウンド タスクを使う利点は、ユーザーのアクティビティの邪魔をせず、AC 電源に接続されているときのみ実行されることが保証されている点です。ですから、バッテリの消耗を心配する必要はありません。
以下のサンプル コードでは、メンテナンス バックグラウンド タスクが、ProcessPictures クラスを呼び出してタスク実行時にファイルを処理します。このタスクは、8 時間おきに実行され、処理する新しいファイルを探します。デバイスがバッテリ電源に移行するとメンテナンス バックグラウンド タスクがキャンセルされるため、バックグラウンド タスクにはキャンセル ハンドラーも登録されています。キャンセル ハンドラーでは、ファイルの処理がキャンセルされます。5 秒以内にキャンセル ハンドラーから戻らなければ、Windows はアプリを終了します。これらのサンプル コードは、バックグラウンド タスクとトリガー クラスに関する知識があることを前提としています。これらのクラスの詳細については、Dev Center (英語) で Windows.ApplicationModel.Background 名前空間に関するドキュメントをご覧ください。
public sealed class MaintenanceBackgroundTask: IBackgroundTask { private ProcessPictures processPic; public MaintenanceBackgroundTask() { // Code to process the pictures processPic = new ProcessPictures(); } //Main Run method which is activated every 8 hours async void IBackgroundTask.Run(IBackgroundTaskInstance taskInstance) { taskInstance.Canceled += taskInstance_Canceled; // Because these methods are async, you must use a deferral // to wait for all of them to complete BackgroundTaskDeferral deferral = taskInstance.GetDeferral(); List<StorageFile> list = new List<StorageFile>(); int count = await processPic.EnumerateFiles(list); bool retval = await processPic.ProcessFiles(list); deferral.Complete(); } // Cancel handler, called whenever the task is canceled void taskInstance_Canceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason) { // Device is now on DC power, cancel processing of files processPic.Cancel = true; } }
// This JS lives within maintenanceBackgroundTask.js var processPic = new processPictures(); var count = 0; var retval = false; function onCanceled(cancelSender, cancelReason) { // Device is now on DC power, cancel processing of files processPic.cancel = true; } backgroundTaskInstance.addEventListener("canceled", onCanceled); var list = []; processPic.enumerateFiles(list).then(function (value) { count = value; processPic.processFiles(list).then(function (value) { retval = value; // Call close() to indicate the task is complete when // all async methods have completed close(); }); });
以下のサンプル コードは、メイン アプリからメンテナンス バックグラウンド タスクを登録する方法を示しています。このバックグラウンド タスクは 8 時間おきに起動され、画像ドキュメント ライブラリ内のすべての画像を処理します。この処理を行う必要がなくなった場合は、IBackgroundTaskRegistration クラスの Unregister メソッドを使ってバックグラウンド タスクの登録を解除できます。
//Registering the maintenance trigger background task private bool RegisterMaintenanceBackgroundTask() { BackgroundTaskBuilder builder = new BackgroundTaskBuilder(); builder.Name = "Maintenance background task"; builder.TaskEntryPoint = "MaintenanceTask.MaintenaceBackgroundTask"; // Run every 8 hours if the device is on AC power IBackgroundTrigger trigger = new MaintenanceTrigger(480, false); builder.SetTrigger(trigger); IBackgroundTaskRegistration task = builder.Register(); return true; }
function registerMaintenanceBackgroundTask() { var builder = new Windows.ApplicationModel.Background.BackgroundTaskBuilder(); builder.name = "Maintenance background task"; builder.taskEntryPoint = "js\\maintenanceBackgroundTask.js"; //Run every 8 hours if the device is on AC power var trigger = new Windows.ApplicationModel.Background.MaintenanceTrigger(480, false); builder.setTrigger(trigger); var task = builder.register(); return true; }
バックグラウンド タスクは、アプリ マニフェストで宣言する必要があります。まず、Visual Studio でアプリのマニフェストを開き、[宣言] タブのドロップダウン リストからバックグラウンド タスクの宣言を追加します。適切なタスクの種類を選択し、JavaScript バックグラウンド タスクを使う場合はエントリ ポイント (バックグラウンド タスクのクラス名) または開始ページを指定します。
マニフェストの内容は、右クリックして [コードの表示] を選択すると表示できます。メンテナンス タスクは、システムに用意されたホストでしか実行できないため (JavaScript の場合は backgroundTaskHost.exe または wwahost.exe)、Executable 属性は指定できない点に注意してください。このマニフェスト スニペットからわかるように、メンテナンス トリガーのタスクの種類は systemEvent です。
<Extension Category="windows.backgroundTasks" EntryPoint="MaintenanceTask.MaintenaceBackgroundTask"> <BackgroundTasks> <Task Type="systemEvent" /> </BackgroundTasks> </Extension>
JavaScript では、StartPage 属性により EntryPoint が配置されます。
<Extension Category="windows.backgroundTasks" StartPage="js\maintenaceBackgroundTask.js">
バックグラウンド タスクの使用の詳細については、「バックグラウンド タスクについて」(英語) というホワイトペーパーと API の使用に関する Dev Center (英語) をご覧ください。
この例では、アプリを定期的かつ頻繁にバックグラウンドで実行する必要があります。アプリをロック画面に配置することでこれを行います。
ユーザーが Windows 8 デバイスを使ていないときでも、アプリはバックグラウンド タスクを使って常に最新の状態に保たれるため、ユーザーは、ロック画面での表示をアプリに許可することで、アプリがそれらのバックグラウンド タスクを使えるかどうかを制御します。ロック画面は、ユーザーが Windows 8 デバイスのロックを解除しなくてもアプリに関する情報を確認できるように設計されているため、これはもともとの性質にぴったり合います。この関係は、対面式の道路のようなものです。アプリは、ロック画面に配置されている場合のみこれらの種類のバックグラウンド タスクを使うことができ、同様に、アプリはこれらの種類のバックグラウンド タスクの使用を要求した場合のみロック画面に表示されます。
図 2 - ロック画面のカスタマイズ UI とロック画面対応アプリ
ロック画面に配置できるアプリの数は比較的少ないため、アプリのロック画面エクスペリエンスを優れたものにすることが重要です。そうでないと、ユーザーはスペースを他のアプリに使えるようにアプリを削除してしまうでしょう。この型にぴったり合うアプリの例としては、通信アプリ (未読電子メール メッセージ数が表示されるメール アプリ、詳細なステータス スロットに今後の予定が表示される予定表アプリ、ユーザーが見逃したメッセージの数が表示されるメッセージング アプリなど) があります。ロック画面に表示してもらえるアプリにする方法の詳細など、ロック画面の詳細については、Dev Center (英語) をご覧ください。
以下のサンプル コードでは、インターネットが使用可能な場合に BackgroundTaskBuilder クラスを使って 15 分おきに実行される時間トリガー バックグラウンド タスクを登録する方法を示しています。インターネットが使用可能でない場合、バックグラウンド タスクは実行されません。代わりに、インターネットが使用可能になるまで待機してから、自動的に実行されます。バックグラウンドのこの特徴も、不要な作業が行われるのを防ぎ、デバイスのバッテリ寿命を節約するため役立ちます。条件がないと、アプリ コードは実行された後ネットワーク接続がないことを検出し、メールをダウンロードできなかったためエラーが発生します。メールは、アプリがフォアグラウンドであるかどうかにかかわらずダウンロードされます。デバイスが Connected Standby (接続維持スタンバイ) の場合もダウンロードされます。
private bool RegisterTimeTriggerBackgroundTask() { BackgroundTaskBuilder builder = new BackgroundTaskBuilder(); builder.Name = "Pop mail background task"; builder.TaskEntryPoint = "MailClient.PopMailBackgroundTask"; // Run every 15 minutes if the device has internet connectivity IBackgroundTrigger trigger = new TimeTrigger(15, false); builder.SetTrigger(trigger); IBackgroundCondition condition = new SystemCondition(SystemConditionType.InternetAvailable); builder.AddCondition(condition); IBackgroundTaskRegistration task = builder.Register(); return true; }
function registerTimeTriggerBackgroundTask() { var builder = new Windows.ApplicationModel.Background.BackgroundTaskBuilder(); builder.name = "Pop mail background task"; builder.taskEntryPoint = "js\\popMailBackgroundTask.js"; //Run every 15 minutes if the device has internet connectivity var trigger = new Windows.ApplicationModel.Background.TimeTrigger(15, false); builder.setTrigger(trigger); var condition = new Windows.ApplicationModel.Background.SystemCondition( Windows.ApplicationModel.Background.SystemConditionType.internetAvailable); builder.addCondition(condition); var task = builder.register(); return true; }
時間トリガーは、前に説明したとおりロック画面に配置されたアプリのみ使うことができます。ロック画面への配置をプログラムにより要求するには、BackgroundExecutionManager クラスを使う必要があります。ユーザーがアプリをロック画面に配置しない場合、バックグラウンド タスクは実行されません。その場合、ロック画面を必要としないバックグラウンド タスクの使用に戻すか、ユーザーがアプリを使用しているときに処理を実行する必要があります。こうするとユーザーが何度も何度も確認を求められることはなくなり、一度だけ確認メッセージが表示されます。ユーザーがいいえを選択したが後で追加したくなった場合、手動で追加することができます。
public async Task<bool> ObtainLockScreenAccess() { BackgroundAccessStatus status = await BackgroundExecutionManager.RequestAccessAsync(); if (status == BackgroundAccessStatus.Denied || status == BackgroundAccessStatus.Unspecified) { return false; } return true; }
function obtainLockScreenAccess() { Windows.ApplicationModel.Background.BackgroundExecutionManager.requestAccessAsync().then(function (status) { if (status === Windows.ApplicationModel.Background.BackgroundAccessStatus.denied || status === Windows.ApplicationModel.Background.BackgroundAccessStatus.unspecified){ return false; } return true; }); }
以下のサンプル コードは、15 分おきに電子メールをダウンロードするメイン バックグラウンド タスクを示しています。アプリのタイルとバッジを更新する方法については、以前のブログ記事「優れたタイル エクスペリエンスを開発する」で説明されているため、詳しく説明していません。
void IBackgroundTask.Run(IBackgroundTaskInstance taskInstance) { int count = popmailClient.GetNewMails(); // Update badge on lock screen with the mail count popmailClient.UpdateLockScreenBadgeWithNewMailCount(count); IList<string> mailheaders = popmailClient.GetNewMailHeaders(); // Update app tile with the subjects of the email popmailClient.UpdateTileWithSubjects(mailheaders); }
//This JS lives within popMailBackgroundTask.js var count = popmailClient.getNewMails(); // Update badge on lock screen with the mail count popmailClient.updateLockScreenBadgeWithNewmailCount(count); var mailheaders = popmailClient.getnewMailHeaders(); // Update app tile with the subjects of the email popmailClient.updatetileWithSubjects(mailheaders); close();
アプリをロック画面に追加するには、マニフェストの [アプリケーション UI] タブでロック画面通知の種類を指定する必要があります。
図 3 - バックグラウンド タスクのマニフェストの宣言
以下に示すのは、ロック画面に配置して、ロック画面にバッジとトーストを表示する必要があるアプリのマニフェスト (ウィザードにより生成されます) のコード スニペットです。時間トリガー タスクは、システムに用意されたホストでしか実行できないため (JavaScript の場合は backgroundTaskHost.exe または wwahost.exe)、Executable 属性は指定できません。タスクの種類は、マニフェストからわかるようにタイマーです。
<LockScreen Notification="badgeAndTileText" BadgeLogo="Images\badgelogo.png" /> <DefaultTile ShowName="allLogos" WideLogo="Images\tile-sdk.png" ShortName="LockScreen CS" /> <SplashScreen Image="Images\splash-sdk.png" BackgroundColor="#FFFFFF" /> </VisualElements> <Extensions> <Extension Category="windows.backgroundTasks" EntryPoint="MailClient.PopMailBackgroundTask"> <BackgroundTasks> <Task Type="timer" /> </BackgroundTasks> </Extension>
JavaScript では、StartPage 属性により EntryPoint が配置されます。
<Extension Category="windows.backgroundTasks" StartPage="js\popMailBackgroundTask.js"
コントロール パネルやプッシュ通知など、他のバックグラウンド タスク トリガーを使って、さらに高度な VIOP、インスタント メッセージング、プッシュ電子メール アプリを構築することができます。それらの使用方法についてはこの記事の範囲を超えているため、詳細についてはホワイトペーパー「バックグランド ネットワーク」(英語) をご覧ください。
既にお話ししたとおり、バックグラウンド タスクは電源効率を考慮に入れて設計されているため、CPU とネットワーク リソースの利用の制約が適用されます。これにより、ユーザーが気付かない間に、バックグラウンドのアプリによってデバイスのバッテリが消耗するのを防ぐことができます。アプリがアクティブで、ユーザーがフォアグラウンドでそのアプリを操作している場合、アプリのバックグラウンド タスクには CPU やネットワーク リソースの制約が適用されません。
ロック画面上の各アプリは、15 分おきに 2 秒の CPU 時間を受け取り、これはアプリのすべてのバックグラウンド タスクが使うことができます。15 分が経過するたびに、ロック画面上の各アプリは、バックグラウンド タスクが使うことができる 2 秒間の CPU 時間をもう一度受け取ります。15 分の間に使用されなかった CPU 時間は失われ、累積されません。ロック画面に配置されていない各アプリは、2 時間おきに 1 秒間の CPU 時間を受け取ります。アプリが使用可能な CPU 時間をすべて使った場合、CPU クォータ更新の次回生成時にアプリの CPU クォータがいっぱいになるまで、そのバックグラウンド タスクは中断されます。
CPU 使用時間とは、アプリが実際に使う CPU の量を指しており、バックグラウンド タスクの実時間ではありません。たとえば、リモート サーバーがコードに応答するのをバックグラウンド タスクが待機していて、実際には CPU を使っていない場合、この待機時間は CPU クォータとしてカウントされません。
CPU リソース クォータ |
更新間隔 |
|
ロック画面対応アプリ |
CPU 時間 2 秒 |
15 分 |
ロック画面非対応アプリ |
CPU 時間 1 秒 |
2 時間 |
ネットワークを使うとデバイスのバッテリがかなり消耗されるため、バックグラウンド タスクの実行時はネットワークの使用も制約されます。ただし、デバイスが AC 電源に接続して実行されている場合、バックグラウンド タスクはネットワークの制約を受けません。バックグラウンド タスクによる CPU の使用は、デバイスが AC 電源に接続して実行されている場合でも常にリソースが制約されます。
以下の表は、リソースが制約された WiFi ネットワークでのネットワーク データ スループットの特徴を示しています (干渉は最小限と想定しています)。
|
|
| |
アプリごとにクォータが割り当てられていますが、それらの固定リソース制約では十分でないことがあります。このような状況のため、アプリケーションが CPU およびネットワーク リソースを共有できる共有グローバル プールが用意されています。
バックグラウンド タスク、グローバル プール、そのリソース管理の制約、ベスト プラクティスの詳細については、ホワイトペーパー「バックグラウンド タスクについて」(英語) をご覧ください。ソース付きのサンプル (英語) もあります。
このように、"アプリは、画面にないときも処理を実行できるか" という質問には、はっきり "はい" と答えることができます。Windows 8 のバックグラウンド モデルでは、アプリは主要なエンド ユーザー シナリオ (ファイルのダウンロード、オーディオの再生、バックグランドでの電子メールの更新、デバイスが AC 電源に接続されているときのメンテナンス タスクの実行など) に対応できます。また、プラットフォームはこれらのアクティビティをしっかりと監視するため、バックグラウンド処理がフォアグラウンド アプリの応答性やデバイスのバッテリ寿命に与える影響は最小限になります。
Windows 8 のバックグラウンド モデルの技術的な詳細については、Dev Center で各種ホワイト ペーパーをご覧ください。ご質問がある場合は、ご遠慮なくこの記事にコメントを投稿してください。できる限りお答えします。
-- Windows プログラム マネージャー Hari Pulapaka
Jake Sabulsky 氏、Johnny Bregar 氏、Kyle Beck 氏、Suhail Khalid 氏のご協力に感謝いたします。また、Alexander Corradini 氏、Arun Kishan 氏、Ben Srour 氏、Ian LeGrow 氏、Jamie Schwartz 氏、John Sheehan 氏など、貴重なフィードバックをいただいた他の方々にも感謝いたします。
リンク |
タイプ |
ハイライト |
バックグラウンド タスクについて (英語) |
ホワイトペーパー |
バックグラウンド タスクについて紹介 |
ドキュメント |
バックグラウンド モデル API 名前空間 |
|
サンプル プロジェクト |
バックグラウンド タスクの使用をデモンストレーション |
|
ロック画面の概要 (英語) |
概念の説明 |
ロック画面とそのベスト プラクティスについて説明 |
バックグラウンド ネットワーク (英語) |
ホワイトペーパー |
バックグラウンド タスクを使った VOIP やインスタント メッセージングなどの高度なアプリを開発する方法について説明 |
ブログ記事 |
優れたタイル エクスペリエンスを開発する方法について説明 |