Android アプリ開発における SQLite のロックとマルチスレッドの話
Android アプリ開発で SQLite を使っていると、しばしば次のような例外が投げられることがあります。*1
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
軽く調べてみたところ
このエラーをぐぐってみると、「複数スレッドから SQLite を使う場合に、それぞれのスレッドで異なる SQLiteDatabase
オブジェクトを使っているとこのエラーが出ることがある」 とか書かれていて、回避策として 「複数スレッドで SQLite を使う場合は 1 つの SQLiteDatabase
オブジェクトを複数スレッドで使いまわすこと」 というような情報が得られました。 あとは当たり前ですが 「トランザクションがちゃんと閉じられているか確認すること」 とか。
いくつかの質問やブログ記事を見てみましたが、いずこも上のようなことが書かれていて、具体的にこの例外が発生する条件などがよくわかりませんでした。
- 今度からSQLiteOpenHelperのDatabaseHelperのインスタンス取得はSingletonパターンにするよ・・・ - Androidはワンツーパンチ 三歩進んで二歩下がる
- Androidのデータベース周りでよくわからないエラーが出る | 技術者のたまごブログ
- Android Database Locked - Stack Overflow
あるスレッドでトランザクションを張っているときに別のスレッドで挿入などを行おうとすると 『SQLiteDatabaseLockedException
が投げられます』、ということが書かれていたブログ記事も見つけたのですが、手元で確認したところ、トランザクションを張ってる時に別スレッドで insertOrThrow
メソッドを呼び出しても必ずしもエラーになるわけじゃなくて、やっぱりよくわかんないなぁという感じでした。
そんなわけなのでちょっと詳細を調べてみることにしました。
調査
下記のようなテストコードを書いて動作を調べました。
次のような環境で実行しました。
API level によって待てる時間が違ってたりするっぽい (ちゃんと調べてない) ので、環境によっては一部のテストに失敗するかもしれません。
結果として
結論としては、次のような知見が得られました。
- “database is locked” というのは Android SDK レベルではなく SQLite レベルで発生している
- ロック取得待ち開始から一定時間が経過すると発生する
- 「一定時間」 は
sqlite3_busy_timeout
関数 で指定される - Android SDK のソースコード的には android_database_SQLiteConnection.cpp の
BUSY_TIMEOUT_MS = 2500;
のあたり を見ればよさそう (API level 18 の Android SDK では 2500 ms で設定されてるっぽい)
- そんなわけなので、とあるスレッドでトランザクションが張られていて、別のスレッドで別の
SQLiteDatabase
オブジェクトを使ってinsertOrThrow
メソッドによる行の挿入などを行おうとしたとき、ロック取得を待って一定時間が経過すると “database is locked” というような例外が発生する - 複数スレッドで同じ
SQLiteDatabase
オブジェクトを使っていれば、“database is locked” というような例外が発生することはない (SQLite レベルでロック取得を待っているのではなく、Android SDK レベルでロック取得を待つため) - 複数スレッドで同じ
SQLiteDatabase
オブジェクトを使う場合と、異なるSQLiteDatabase
オブジェクトを使う場合の (使用者側から見たときの) 違いは次のようなことしか無さそう- ロック取得を待って一定時間が経過したときに例外が送出されるかどうか
query
メソッドと、その返り値のCursor
オブジェクトに対する操作で行取得を行う場合の挙動:- 同じ
SQLiteDatabase
オブジェクトを使っている場合はquery
メソッドで他スレッドのトランザクション終了を待つ (他スレッドでのトランザクションが immediate モード (non exclusive) でも exclusive モードでも。) - 別の
SQLiteDatabase
オブジェクトを使っている場合は、他スレッドのトランザクションが exclusive モードならCursor
オブジェクトへの操作時点で他スレッドのトランザクション終了を待つし、他スレッドのトランザクションが immediate モード (non exclusive) ならトランザクション終了を待たずに結果を返す
- 同じ
ベストプラクティス?
ベストプラクティス的なものはまだはっきりとは見えてないのですが、ぐぐって出てきた情報や今回調べたことなどを元に今のところわかっていることを書いておくと次のようになります。
- 複数スレッドで SQLite とやり取りする場合、1 つの
SQLiteDatabase
オブジェクトを複数スレッドで使うようにしても、(おそらくだが) 問題はない- あるスレッドで
beginTransaction
したら、別のスレッドではトランザクションの終了を待つようになっている - ただし、immediate モードのトランザクションはさすがに Java のライブラリのレベルではサポートできていない
- あるスレッドで
- Exclusive モードのトランザクションを張っている場合は、他スレッドでの行の挿入や削除はもちろん、行の取得も待たせてしまうので、秒単位の時間がかかるトランザクション処理は書かないようにすべき
- 万一、秒単位のトランザクションが発生しても “database is locked” の例外が発生しないように、アプリケーション全体で 1 つの
SQLiteDatabase
オブジェクトを使いまわすというのは有効- だが、その場合は immediate モードのトランザクションを張っても、他のスレッドでは行の取得もトランザクションが終わるのを待ってしまう
- ので、immediate モードのトランザクションを使って、データ書き込み中でも別スレッドで読み取りができるようにするには、読み取り用の
SQLiteDatabase
オブジェクトと書き込み用のSQLiteDatabase
オブジェクトの少なくとも 2 つは使い分ける必要がある
- 当たり前だけど
SQLiteDatabase#beginTransaction
して、SQLiteDatabase#endTransaction
しなかったら、後で DB への行挿入などしようとしたときに “database is locked” って言われるので、トランザクション中に例外が発生してもSQLiteDatabase#endTransaction
が呼ばれるようなコードにしておくこと - 今回の話とは関係ないけど、複数回の行挿入をする場合など、必要がなくてもトランザクションを張った方が処理速度が速くなるらしい
- 接続を新たに張るのにかかるコストとか、必要ないときまで接続しっぱなしにしておくのにかかるコストとか、そこら辺は全然考えてないので、必要な時に接続を張って使い終わったら閉じるのがいいのか、それともずっと接続を開きっぱなしにしておくのがいいのかは不明
詳細
- あるスレッドにおいて
SQLiteDatabase#beginTransaction
やSQLiteDatabase#beginTransactionNonExclusive
でトランザクションが張られている場合に、別のスレッドで別のSQLiteDatabase
オブジェクトを使用して次のメソッドを呼び出した場合、一定時間 (2 秒程度?) が経過するとSQLiteDatabaseLockedException
例外 *2 が発生する - 一定時間が経過するまではロックが解除される (別スレッドでのトランザクションが閉じられる) のを待つ
- トランザクションを張るのに使用している
SQLiteDatabase
オブジェクトと同じオブジェクトを使って別スレッドで上のようなメソッドを呼び出した場合は、一定時間が経過しても例外が発生せず、トランザクションが閉じられるのを待ち続ける *3 - あるスレッドにおいて
SQLiteDatabase#beginTransaction
メソッドでトランザクションが張られている場合に、SQLiteOpenHelper
を使って新しいSQLiteDatabase
オブジェクトを生成しようとした場合は、やはり一定時間まではトランザクション終了を待つが、それを経過するとSQLiteDatabaseLockedException
例外 *4 が発生する SQLiteDatabase#beginTransactionNonExclusive
メソッドでトランザクションが張られている場合は、SQLiteOpenHelper
を使ってトランザクションが閉じられるのを待たずに新しいSQLiteDatabase
オブジェクトを生成できる- あるスレッドにおいて
SQLiteDatabase#beginTransaction
でトランザクションが張られている場合に、別のスレッドでSQLiteDatabase#query
メソッドを呼び出し、返り値のCursor
オブジェクトのgetCount
やmoveToFirst
メソッドを呼び出すという処理をする場合- トランザクションを張るのに使用した
SQLiteDatabase
オブジェクトを使用しているならば、query
メソッド呼び出しの時点でトランザクションが閉じられるのを待つ - トランザクションを張るのに使用した
SQLiteDatabase
オブジェクトと別のオブジェクトを使用しているならば、Cursor
に対するメソッド呼び出しの時点でトランザクションが閉じられるのを待つ - ちなみに数秒待ち続けても
SQLiteDatabaseLockedException
例外 *5 は発生しない
- トランザクションを張るのに使用した
- あるスレッドにおいて
SQLiteDatabase#beginTransactionNonExclusive
でトランザクションが張られている場合に、別のスレッドでSQLiteDatabase#query
メソッドを呼び出し、返り値のCursor
オブジェクトのgetCount
やmoveToFirst
メソッドを呼び出すという処理をする場合- トランザクションを張るのに使用した
SQLiteDatabase
オブジェクトを使用しているならば、query
メソッド呼び出しの時点でトランザクションが閉じられるのを待つ - トランザクションを張るのに使用した
SQLiteDatabase
オブジェクトと別のオブジェクトを使用しているならば、トランザクションが閉じられるのを待たずに結果を得られる
- トランザクションを張るのに使用した
プログラムに対する適切なコメント付けについて考えてる
上記記事を読んだ。 「コメントを入れるか入れないか」 について言及されていて、ちょうど最近、人から 「nobuoka はコメントを書きすぎだと思う」 と指摘されたところだったので、『Code Complete』 に興味がわいた。 『Code Complete』 はそのうち読むとして、そういう本を読んでみる前に自分のコメントについて振り返ってみようと思う。
職業プログラマーなら必ず読むべき「Code Complete」 - $shibayu36->blog;コメントを入れるか入れないか
- これまでのコメント論をきちんと表しており、この部分は本当におもしろかった
- 結論としては
- ややこしいコードの意味をコメントで説明するくらいならコードを綺麗にすべきだが、コードよりも抽象的レベルで説明するコメントは絶対に必要
- 特に複数人なら。一人でも未来の自分に向けて必要。
- しかし、コードを復唱するコメントは最悪である
- コメントの最適な数としては大体10ステートメントに1個くらいで、プログラムが最も明瞭になるという研究がある
なぜコメントを書くのか
私がコメントを書くのは、基本的には将来の自分や他人がそのコードを読むときに読みやすくなるように、という目的である。 これはおそらく大多数のプログラマがそうなのではないかと思う。
その目的部分ではみんな合意できると思うのだけれど、問題は 「どういうコメントがあると読みやすいと感じるのか」 あるいは 「読みにくいと感じるのか」 という部分はプログラマごとによって違うということだと思う。
なので、読み手として 「自分自身はどういう場合にコメントがあると嬉しいのか」 みたいなのを実例付きで書いてみる。
どういうコメントがあると嬉しいか
自分自身、どういうコメントがあると嬉しいと思っていて、実際にどういうコメントを書いているのか。
コードだけからは読み取れない 「意図」 を説明するコメント
例 1 のように、「なぜそういうことをするのか」 を説明するコメント。
なぜそうするのか、という意図はコードからは読み取れないわけなので、意図を説明するコメントに対して否定的になる人はほとんど居ないと思う。
クラスやメソッド、フィールドなどの説明をするコメント
例 2 のようにクラスの説明をしてたり、例 3 のようにメソッドとその引数などの説明をしてたり、例 4 のように変数の説明をしたりするコメント。 私はコードを読んで理解するのに結構時間がかかるので、こういうコメントがあると嬉しい。
- 例 2 : wscutils の info.vividcode.util.oauth.OAuthEncoder クラスの説明
- 例 3 : wscutils の info.vividcode.util.oauth.OAuthEncoder.encode メソッドの説明
- 例 4 : wscutils の info.vividcode.util.oauth.OAuthEncoder における静的プライベート変数 BS の説明
例 2 や例 4 に関しては 「そもそも名づけが糞である」 と思うし、例 2 だと本来は OAuth 1.0 の RFC の URL でも張っておくべきところだとも思うが、まあそれは今回は良いとしよう。
最近 「こういう説明は実装を読めばわかるから書かないでほしい。 実装を読むときの邪魔になる」 って言われて、そういう人もいるのかー、と思ったりした。 上の例は実装が簡単なのでまあコメントがなくても読むのにかかる時間はそんなに変わらないだろうけど、もうちょっと長かったり難しい処理をしているメソッドなどを読むことを考えると、私の場合はコメントがある方がより速くコードを読める *1 と思う。 少なくとも (読む必要がないコメントでも) 邪魔とは感じないかなー。 まあそこら辺は人それぞれなのかもしれない。
今回挙げた例だと仕様的な部分は全部クラス内に閉じているので、「encode メソッドによるパーセントエンコードの対象になるのは 'A'-'Z', 'a'-'z', '0'-'9', '-', '.', '_', '~' を除く全ての文字である」 ということはクラス内を全部調べればわかることではあるけど、encode メソッドを使いたいときにいちいちクラス内の実装を全部読みたいとは思わない。 それに動的型付けの言語だと、(コメントに書かれていないと) そのメソッドに渡されてくる引数の型さえも呼び出し側を見ないと全くわからない場合もあるわけで、「あるメソッドを使いたいと思った時に、引数に渡すべき型を知るためにそのメソッドを呼び出している別の箇所を調べる」 などをいうことをする必要がある。 個人的にはやりたくないなー。
まあ思想が統一されていないのは、コメントを付けるとか付けないというどちらの方針よりも良くないと思うので、複数人が関わるプロジェクトの場合はそのプロジェクトの指針に従うべきだと思ってる。
処理の塊を説明するコメント
例 5 の removeBackgroundImage メソッドの中のコメントに書いているように、「設定値を変更して保存する」 「見た目を更新する」 「ファイルを削除する」 という具合に、理想的にはそれぞれメソッドに切り出して抽象化すべきような内容を同じメソッドに書く場合などに、処理の塊が明示されるようにコメントを書くことがある。
「理想的にはメソッドに切り出して抽象化すべき」 と上に書いたが、実際のところ 2 行の処理をプライベートメソッドに切り出す必要性はないと思うので、こういうコメントを書いておく形が個人的には良いと思っている。 まあ今回の例だと 2 行の処理に対して 1 行のコメントを付けてるので、「こんなコメント書くな糞が」 と言われても仕方ないかなーという気はするし、個人的なプロジェクトの場合しかこういうことはしてないつもりではある。
TODO など
例 6 のように TODO などが書かれたコメント。
TODO とか FIXME とか XXX とか、そもそもそういうものを書かないで済むようにするのが理想ではあるけど、実際にはそういうのは残ってしまうので、コメントに明記しておくのが良い。 これについても 「意図」 のコメントと同じで否定的に思う人はほとんど居ないと思う。
ドキュメントなどへの誘導
例 7 や 8 のように、ドキュメントの URL などが書かれたコメント。
- 例 7 : MeteorLine の BackgroundImageManager クラスで WinJS.Class.mix を使っている場所から 「カスタム コントロールを構築」 のブログ記事への誘導
- 例 8 : MeteorLine の BackgroundImageManager クラスで Windows ストアアプリにファイル保存している場所からドキュメントへの誘導
自分が探すのに苦労したドキュメントなど、将来の自分がそのコードを読んでドキュメントを見たいと思った時に探すのに苦労すると考えられる場合は URL などを書いておく。 個人プロジェクトの場合はわりと気軽に使うけど、複数人が触るコードの場合は気軽には使わないようにしている。
利用者に対する注意など
例 9 のように 「このメソッドを使うときはこういうことに注意すること」 みたいなコメント。
パブリックなメソッドの場合はこういうコメントを書くべきではなくて、実装側で安全にしておくべき。 一方で、ある範囲内でしか使用されないプライベートなメソッドの場合は、その範囲内の実装者が気を付けられると考えられる場合はコメントに注意点を書いておいて、実装時に注意する、などすればよいと思う。 例えばクラス内にプライベートなメソッドとか。 例 9 の場合はクラス外に公開されているメソッドだけど、個人プロジェクトだし 「プロジェクト内にプライベートである」 と考えられるので、コメントを付けて注意喚起する程度で十分だと思って、そのようにした。
メソッドの実装者が知っておくべき仕様
例 10 の JSON における数値の表記形式の仕様が書かれたコメントのように、メソッドの仕様の説明ではなく、メソッド実装者が知っておくべき仕様を書いたもの。
RFC などの URL を張っておけばいいという話かもしれないけど、実装する立場の人間としてはそのメソッドに関する部分だけコメントで書かれていた方が嬉しい。 しかしその場合でも RFC などの URL は張っておくべき (少なくとも参照した仕様がなんであるかが明確にわかるようにすべき) だと思うので、上のコメントはその点では糞だと思う。
その他
大体そんな感じかなー。 あとは著作権表記をコメントに書いたり、Javadoc とかだとコードサンプルを書いたりすることもあるけど、それはコードとしてのコメントというよりはドキュメントとしてのコメントなのでその他扱いでいいような気がする。 他には、設計のことを書いたりすることはあるかなぁ、という程度か。
適切なコメントについて考えてる
今までコメントがなくて困ったことはあってもコメントがあって困ったことはなかったので、こないだ 「コメントがあると邪魔」 って言われて結構驚きがあった。 なので最近は適切なコメント量、あるいはコメントの種類について考えたりしてる。
shiba_yu36 さんの記事を読んだところ、『Code Complete』 には以下のように書かれてるらしいけど、詳細を知りたい感じだ。
- コードよりも抽象的レベルで説明するコメントは絶対に必要
- コードを復唱するコメントは最悪
自分のコメントはコードを復唱するもののつもりではないけど、実際には復唱するものに分類されるのかなー。 あるいは抽象化のレベルが適切でないとか。 うーん。
info.vividcode.util.json.JsonParser クラス は 2 年前に書いたコードで今見るとひどい感じの実装ではあるけど、コメント付けに関しては今の自分が見ても過不足ないと思う (が、逆にいえば実装をもうちょっとまともにすればコメントは減ると思う) ので、自分自身が自分自身のために書くコメントについてはちゃんとできてるかなーというのが今のところの実感としてある。 問題は複数人で触るコードだよなぁ。 他人のコードの読み書きについての経験は浅いので、そこら辺は経験を積むと同時に本を読んで勉強したいところ。
まあ何はともあれ 『Code Complete』 を読んで、それからまたこの記事に書いたことを振り返ってみよう。
Code Complete第2版〈上〉―完全なプログラミングを目指して
- 作者: スティーブマコネル,Steve McConnell,クイープ
- 出版社/メーカー: 日経BPソフトプレス
- 発売日: 2005/03
- メディア: 単行本
- 購入: 44人 クリック: 1,162回
- この商品を含むブログ (280件) を見る
Code Complete第2版〈下〉―完全なプログラミングを目指して
- 作者: スティーブマコネル,Steve McConnell,クイープ
- 出版社/メーカー: 日経BPソフトプレス
- 発売日: 2005/03
- メディア: 単行本
- 購入: 16人 クリック: 193回
- この商品を含むブログ (158件) を見る
*1:ここで言う 「コードを読む」 というのは、当該メソッドの実装を読む場合と当該メソッドを使用しているコードを読む場合の両方を含んでいる。 また、当該メソッドを初めて読む場合も、既にそのメソッドを知っており記憶がおぼろげながらに存在する場合も両方含んでいる。
Windows ストアアプリ開発において UI 部品をページコントロールで実装する
Windows ストアアプリを JavaScript で開発する話です。 アプリを複数のページで構成する場合、基本的にはベースを単一の HTML ページにして、その上に次々とページコントロールを読み込むことでページ遷移を実現します。
ここで使用する ページコントロール (WinJS.UI.Pages.PageControl オブジェクト) ですが、名前からしてページ全体を表すためのもののように思えます。 しかし、実際にはページ全体に限らずページの一部を構成する UI 部品 (UI コンポーネント) をページコントロールで実装することもできます。
UI 部品をページコントロールで実装する
1 つのページ内に複数の複雑な処理を行う UI 部品を配置する場合 (そして、それらの UI 部品における処理同士はそんなに密接に絡み合っていない場合)、保守することを考えるとそれらの UI 部品はそれぞれ別々のファイルにして実装したいところであります。 また、複数のページに表示したい UI 部品がある場合も、その UI 部品はページ全体のファイルとは別にして管理したいところです。
UI 部品を別ファイルで管理する方法としては色々な方法が考えられますが、最も簡単な方法はページコントロールとして作成することだと思います。 ページコントロールの中にページコントロールをレンダリングする場合にどんなコードを書けばいいのかを書いておきます
基本的なこと : ページコントロールの作り方とページ中へのレンダリングの仕方
一応基本を。 Visual Studio 2012 を使用して Windows ストアアプリを開発している場合、ページコントロールを作るには "ソリューションエクスプローラー" の中のページコントロールを作成したい場所のディレクトリを右クリックして、"追加" → "新しい項目" から "ページコントロール" を選択することでページコントロールのためのファイル (ページコントロールを構成する HTML と CSS, JavaScript の 3 つのファイル) が生成されます。
そして、そのページコントロールをページ中に表示するには以下のような方法があります。 (他にも方法はあります。) ただし、render メソッドやコンストラクタ呼び出しの直後からページの内容が使えるようになるわけではないことに注意が必要です。
// WinJS.UI.Pages.render メソッドを使う方法 var elem = document.getElementById("page-control-host"); WinJS.UI.Pages.render("/pages/pageControl.html", elem); // PageControl オブジェクトコンストラクタを使う方法 var MyPageControl = WinJS.UI.Pages.get("/pages/pageControl.html"); var elem = document.getElementById("page-control-host"); new MyPageControl(elem);
ページコントロール中にページコントロールを埋め込む
さて、ページコントロールの初期化処理の途中で別のページコントロールを読み込む場合、そのページコントロールを埋め込むタイミングを考える必要があります。
ページコントロールのコンストラクタを呼び出して新たなインスタンスを生成する際、ページコントロールのインスタンスに定義されているいくつかのメソッドが順に呼ばれます。
- init : ページコントロールの内容がセットされる前に呼び出される
- load : もともとの HTML ファイルから作られた DOM 木のコピーを行う (普通はオーバーロードせずにデフォルトのまま使えばよい)
- processed : ページコントロールの内容がセットされ、WinJS.UI.processAll() の呼び出しも行われた後に呼び出される
- ready : ページコントロールのレンダリングの一連の流れの最後に呼び出される
これらのメソッドについては WinJS.UI.Pages.IPageControlMembers インターフェイスのドキュメント をご覧ください *1。
これらの処理の中でページコントロール自身に別のページコントロールを読み込ませるために最適なタイミングは processed メソッドの中です *2。 また、読み込んだページコントロールの内容が揃う (読み込んだページコントロールの processed メソッドの処理が完了する) までは、ready メソッドが呼び出されないようにする必要もあります。 これは、processed メソッドの返り値として、子も含めてすべてのページコントロールの処理が完了した時に fulfilled される Promise オブジェクトを返すことで実現できます *3。
これらのことを踏まえたうえで、別のページコントロール (下の例では ChildUI) を読み込むページコントロール (下の例では ParentUI) を定義する最低限の JavaScript の記述は以下のようになります。
(function () { "use strict"; // このページコントロールの中に読み込む別のページコントロール (子のページコントロール) var ChildUI = WinJS.UI.Pages.get("/pages/ChildUI.html"); WinJS.UI.Pages.define("/pages/ParentUI.html", { /// <field type="HTMLElement" domElement="true" hidden="true">このページコントロールの最上位要素</field> element: null, /// <field type="Array">child ui のリスト</field> _childUIs: null, // ここにプロパティを書いておくと IntelliSense の恩恵を受けられる (値は null でいい) init: function (element, options) { // 通常はコンストラクタで行うような処理をここに書けばよい this._childUIs = []; }, processed: function (element, options) { // ページコントロール ChildUI をレンダリングする先の HTML 要素を取得する // (この例では複数個読み込む) var childUIContainers = element.getElementsByClassName("child-ui-container"); // 読み込んだページコントロールの初期化処理が processed まで完了したら fulfilled される // Promise オブジェクトを入れておくための配列 var promisesToBeWaited = []; for (var i = 0, len = childUIContainers.length; i < len; ++i) { var childUI = new ChildUI(childUIContainers.item(i)); this._childUIs.push(childUI); promisesToBeWaited.push(childUI.renderComplete); } // 全ての ChildUI の processed が終わるのを待つ return WinJS.Promise.join(promisesToBeWaited).then(function () { // ChildUI の ready よりも自身の ready を後に実行する様に, イベントループのキューに入っている別の処理を先に実行させる return WinJS.Promise.timeout(0); }); }, ready: function (element, options) { /// <summary>processed の処理が全て終わった後にページコントロールの仕組みによって自動的に呼び出される処理</summary> // イベントリスナの設定とかはここでやればいい }, unload: function () { /// <summary>この UI の終了処理を行う</summary> // 子のページコントロールの unload メソッドを呼び出す this._childUIs.forEach(function (childUI) { if (childUI.unload) childUI.unload(); }); } }); })();
しかしよく考えてみたら
読み込むページコントロールを静的に決定できる場合は、以下のように HTML 側で data-win-control 属性に入れておけばいいですね (下の例は読み込むページコントロールを ChildUI でグローバルに参照できる場合)。 上のような面倒なことしなくていいですし。 unload の呼び出しを忘れないようにしないといけないので、その点だけ注意が必要です。
<div class="child-ui" data-win-control="ChildUI"></div>
読み込むページコントロールを静的には決定できない場合などは JavaScript で読み込む必要があるので、そういう場合は上で述べた方法を使うのがいいのではないでしょうか。
*1:ちなみに、これらのメソッドはデフォルトで定義されていますが、WinJS.UI.Pages.define メソッドを使って新たなページコントロールのコンストラクタを定義する際に、第 2 引数として渡すオブジェクトにこれらを定義しておくことでオーバーライドできます。
*2:ready メソッドは、それが呼び出された時点ですでにページコントロールの中身は使用可能な状態になっている必要があるので、ready メソッドの中では遅すぎます。 また、init メソッドの中ではページコントロールのコンテンツが何もない状態ですので、子のページコントロールを読み込む先の HTML 要素もまだ存在しません。 よって、init メソッドの中では早すぎます。
*3:ページコントロールの初期化処理の中で、processed メソッドが Promise オブジェクトを返した場合、それが fulfilled されるまで ready メソッドは呼び出されないため。