本稿に掲載したコードを試すだけなら、無償のExpressエディションやCommunityエディションで構わない。Windows(製品版)はマイクロソフトのページから無償で入手できる。Desktop」はデスクトップで動作するアプリ用)。4(製品版)もマイクロソフトのページから無償で入手できる。Packの日本語版を追加インストールし、オプションダイアログで言語を切り替える必要がある。本稿では、紛らわしくない限り次の略称を用いる。3/4も)では、残念なことにVB用のユニバーサルプロジェクトのテンプレートは含まれていない*7。そのため、本稿で紹介するコードはC#のユニバーサルプロジェクトだけとさせていただく。14」)からの提供となるようだ。Microsoft)」からの回答による。TIPS:中断時にセッションデータを保存するには?[ユニバーサルWindowsアプリ開発]」(以降、「前回」と呼ぶ)のサンプルコードを出発点とする。前回をご覧になっていない方は、先にお読みいただきたい。中断時にデータをファイルに書き出す場合でも、その処理を行う場所は前回と同じである。アプリ全体のデータを保存するならAppクラスのOnSuspendingメソッドで、画面ごとのデータを保存するならそのコードビハインドのNavigationHelper_SaveStateメソッドで行う。前回はテキストボックスに入力した文字列をセッションデータ(アプリが中断されたときの状態を復元するのに必要なデータ)として中断時に保存したが、これをユーザーデータ(ユーザーが保存したと考えているデータ)として独自のファイルに書き出すように変更してみよう。データを保持しファイルに読み書きする「UserData」クラスを追加する(次のコード)。実際には複数のプロパティを扱う複雑なものになるはずだが、ここではサンプルということでプロパティは一つだけとした。TextBox1Text;}}}データを保持しファイルに読み書きする「UserData」クラスのコード例(C#)このクラスは共有プロジェクトに置く。簡単にするため、以下のような点で実用的なコードとは異なっている。使い勝手からは、プロパティはデータバインドに対応させた方がよい。すると、プロパティに値をセットしたときにSaveAsyncメソッドを呼び出すという作りにせざるを得なくなる。すると、値に変化があったときだけに保存する処理や、短い時間間隔で(例えばテキストボックスでキーが押されるたびに)ファイル書き込みを発生させないためのタイマー処理などが必要になってくる。そのような設計にしたとしても、後述する中断時にファイル書き込み処理が打ち切られるという問題は発生し得る(例えば、テキストボックスでキーを押すとほぼ同時にスタートボタンをタップしたときなど)。タイマー処理を入れると、中断時のファイル書き込みに失敗する可能性が高くなるだろう。データのシリアライズ/デシリアライズは、このコードでは簡略化している。実際にはXmlSerializerクラス(System.Xml.Serialization名前空間)やDataContractJsonSerializerクラス(System.Runtime.Serialization.Json名前空間)などを利用する。ここではローカルフォルダーに保存しているが、ローミングフォルダーに保存する場合には、他のデバイスからデータがローミングされてきたときのイベントに対応する必要がある。そのイベントでデータを読み込み直し、データが変更されたことをUIに対して通知するためのイベントを発火させることになる。8.0のときは使えなかった)。このUserDataクラスを使うように画面のコードビハインドを変更する(次のコード)。UserData.TextBox1Text;......省略......}UserDataクラスを使ってテキストボックスの文字列を保存/復元するコード例(C#)前回のコードからの変更点を太字で示す。これは画面のコードビハインドである。UserDataクラスのLoadAsyncメソッドは、アプリの起動時に1回だけ呼び出せばよい。コードの簡素化のため、この場所に書いている。NavigationHelper_SaveStateメソッドのシグネチャには、asyncキーワードの追加が必要だ。これで実行してみると、ほとんどの場合には、テキストボックスに入力した文字列が中断時にファイルへ書き出されるはずだ。Windowsの場合は簡単にファイルを見られるので、確認してほしい。ローカルフォルダーの「UserData.txt」ファイルである(ローカルフォルダーの場所はWindows.Storage.ApplicationData.Current.LocalFolder.Pathプロパティで取得できる)。非同期処理に起因するトラブル!上のコードではファイルに書き出す処理がごく短時間で済んでいるため、あまり失敗することはないだろう。運悪く失敗することがある。そのとき、何が起きるのだろうか?ファイルに書き出す処理が長引いたときに何が起きるかを確認するため、UserDataクラスのSaveAsyncメソッドに一定時間待機するコードを追加する(次のコード)。Serialize());}ファイルに書き出す処理が長引いたときの現象を確認するためのコード例(C#)ファイルを書き込む前に1秒間だけ待機するようにした(太字の部分)。awaitしているので、このメソッドの呼び出し元へは直ちに制御が返る。これで実行してみてほしい(デバッグ実行ではなく、スタート画面から起動する)。中断時にファイルへデータが書き出されないはずだ。そんなことになるのかというと、NavigationHelper_SaveStateメソッドの返値がvoidだからである。Taskオブジェクトを返さないので、呼び出し元は非同期処理の完了を待ってくれないのだ。システムは、このファイル書き込み処理の完了を待たずに、中断状態へアプリを移行させてしまうのである。そのため、上の例のように全く書き込まれなかったり、複数行のファイルが途中で途切れてしまったりするのだ。AppクラスのOnSuspendingメソッド内で待機して解決するこの問題を解決するには、AppクラスのOnSuspendingメソッドの中でファイル書き込みの完了を待てばよい(次のコード)。OnSuspendingメソッドでは、非同期処理の完了を待つようにシステムへ指示できるからだ。非同期処理が完了したことをシステムに通知するdeferral.Complete();}AppクラスのOnSuspendingメソッドの中でファイル書き込みの完了を待つコード例(C#)太字の部分を追加した。このUserDataクラスのWaitFileIOAsyncメソッドについては、すぐ次で説明する。プロジェクトテンプレートで自動生成された部分に、非同期処理の完了をシステムに待たせる処理が入っている。引数eのSuspendingOperationプロパティのGetDeferralメソッドを呼び出すことで、このOnSuspendingメソッドが非同期処理を行うことをシステムに通知している。メソッド末尾に置かれたCompleteメソッドの呼び出しで、非同期処理の完了をシステムに知らせているのだ。OnSuspendingメソッドに許された時間は5秒までであり(前回参照)、それを超えるとアプリは強制終了させられる(デバッグ実行時を除く)。画面遷移時にはユーザーデータを保存しない(=中断時に保存するだけでよい)というのであれば、UserDataクラスのSaveAsyncメソッドの呼び出しを、画面のNavigationHelper_SaveStateメソッドではなく、このAppクラスのOnSuspendingメソッドで行えばよい。その場合、WaitFileIOAsyncメソッドは必要ない。上のコードは、UserDataクラスにファイル書き込みの完了を待つ「WaitFileIOAsync」メソッドがあるものとして書いてある。いろいろな方法が考えられるが、ここでは排他ロックを使って実装してみよう(次のコード)。ファイルに書き出す処理をロックで囲む。上のWaitFileIOAsyncメソッドはロック確保待ちとなりリターンできない。ファイルからの読み出し処理もロックで囲んでおく。元の画面へ戻す操作が行われる(すると、このLoadAsyncメソッドが呼び出される)可能性がある。FileIO.ReadTextAsync(file));}}}スレッド間の排他ロックを使ってファイル書き込みの完了を待機するコード例(C#)太字の部分を追加した。TIPS:非同期:awaitを含むコードをロックするには?(AsyncLock編)[C#、VB]」を参照していただきたい。これで実行して、中断時の挙動を確かめてほしい(スタート画面から実行する)。今度は正常にファイルへ書き込まれるはずである。まとめ中断時にデータをファイルへ書き出す場合、画面のコードビハインドで処理を行うと、その非同期処理の完了をシステムが待ってくれないために問題が生じる。解決するには、AppクラスのOnSuspendingメソッドの中でファイル書き込みの完了を待つ工夫をする。メールマガジンをご購読ください。申し込みページへITmediaはアイティメディア株式会社の登録商標です。