Windows ストア アプリで
スレッド間の排他はどうする?
2014/5/10 @biac
非同期勉強会
2014/5/10 1
スピーカー紹介: 山本 康彦
• 宇宙世紀以前の生まれ
スプートニク1号より3ヶ月ほど前
• 最初は HONDA
クルマの設計/研究を10年くら
いやってた
• 今は BluewaterSoft
を名乗ってアプリ開発とか技術
解説記事とか
• 昨年7月に出した本⇒
技術評論社「C#による マルチコアのための 非同期/並列処理プ
ログラミング」
2014/5/10 2
今日の話は…
• 前半は、この本の
PART4 から
Windows ストア アプリで非同
期/並列処理の例
• 後半は、この本には書
かなかった話
Windows ストア アプリの場合、
スレッド間でロックするには?
2014/5/10 3
WINDOWS ストア アプリで
非同期/並列処理の例
しばらく前振りだよ
2014/5/10 4
async/await, Parallel/PLINQ …
• このごろの .NET Framework
非同期/並列処理も手厚くサポート
• 非同期: async/await キーワード
非同期メソッドの呼び出しと、その後のコール
バック処理をスムースにコーディング
• 並列: Parallelクラス/PLINQ
ループ処理を自動的に並列化
2014/5/10 5
Windows デバイス アプリ
• いわゆる「Metro スタイル アプリ」
Windows 8.x, Windows Phone 7~,
近い将来 Xbox も解禁か?
さらには IoT (Internet of Things) にも
• async/await を多用したコーディング
用意されている API が非同期ばっかりだから!
2014/5/10 6
Metro アプリやってる人は、
もうとっくに慣れたよね!? f(^^;
非同期/並列化の例
• この本の PART 4
ソースコードはダウンロード可
http://gihyo.jp/book/2013/978-4-
7741-5828-0/
2014/5/10 7
サポート ページ
からダウンロード
できます
Langton's loops (ラングトンのループ)
• 詳しくは Wikipedia 参照 (できれば英語版を)
2014/5/10 8
セル オート
マトンの一種
Langton's loops
2014/5/10 9
セルの状態は、
・四方のセルの状態に依存
・219パターン
これを全てのセルごと (画
面上のドットごと) に計算
する ⇒ けっこうな計算量
Langton's loops
• すなおに実装してみる
1. 四方の値を読み取って配列に格納
する (×セルの数だけ繰り返す)
2. 次の状態を計算する (×N回)
3. 画面を描き変える
4. 1.に戻る
2014/5/10 10
表示を
描き変える
四方を観測する
×N回
次の状態を決定する
×N回
Langton's loops
• 素直な実装で動かしてみる
2014/5/10 11
0.08秒
/ サイクル
Langton's loops
• 非同期化+並列化+α
・計算と画面描画を非同期化
Task, async/await
・観測と状態決定のループを並列化
Parallel.For
・その他、ロジックのチューニング
2つのループを1つに etc.
2014/5/10 12
表示を
描き変える
四方を観測する
×N回
次の状態を決定する
×N回
並列実行
並列実行
バッ
ファ
配列
Langton's loops
• 非同期+並列化+α の効果は!?
2014/5/10 13
0.009秒
/ サイクル
1ケタ速く
なった!!
Langton's loops
• 非同期化+並列化+α
大事な話: スレッド間のロック
を使っていない!
トラブルの元になるロックは、なる
べく使わずにすむように設計する
2014/5/10 14
表示を
描き変える
四方を観測する
×N回
次の状態を決定する
×N回
並列実行
並列実行
バッ
ファ
配列
ロックは敵だ
• ロックするからデッドロックに嵌まる
ロックしなけりゃデッドロックは起きない
マルチスレッドもデータベースも
• ロック不要の設計を心掛ける
• それでも必要なら当然使う
.NET Framework には、単純なロック以外にも
便利な仕掛けがあるよ♪
2014/5/10 15
WINDOWS ストア アプリで
スレッド間の排他処理
さて、ここからが本題
2014/5/10 16
スレッド間競合の例【1】
• Windows ストア アプリでよくある例
• 例えば、複数のダウンロード
と後処理を並列で行う
⇒ 後処理で同じリソースを
更新すると、競合
2014/5/10 17
ダウンロード
×N
データ処理
×N
並列実行
バイン
ディング
ソース
並列実行
スレッド間競合の例【1】
• 注: 普通は、時間の掛かるダウンロードだ
け並列実行にして、後処理はシリアルに
やればいい。
※ @IT「Windowsストア・アプリ開発入門」第6回 のサンプルコードのコ
メントを参照
2014/5/10 18
スレッド間競合の例【2】
• バックグラウンド タスクの終了トリガー
⇒ いつ呼び出されるか分からない
• 通常処理でも使うメソッドをトリガーか
ら呼び出していると、競合する
2014/5/10 19
private async void
FooAsync()
{
……
}
UI スレッドから呼び出し
UI スレッド
private async void Button_Click(…)
{
await FooAsync();
バックグラウンド タスク
のスレッドから呼び出し
終了トリガーを受けるメソッド (別スレッド)
private async void onTaskCompleted(…)
{
await FooAsync();
スレッド間競合の例【3】
• UI のイベント ハンドラー
⇒ 呼び出し側は await してくれない
未完了でも次のイベントが走る
2014/5/10 20
private async void
FooAsync()
{
……
}
UI スレッド
private async void Button_Click(…)
{
await FooAsync();
}
❶
❶
❷
❶が await で抜ける
FooAsync 未完了でも❷が走る
❷
【3】のサンプル コード
2014/5/10 21
UI スレッド private async void UpButton_Click(……)
{
await AddAsync(+1); // ⇒次ページ
}
private async void DownButton_Click(……)
{
await AddAsync(-1); // ⇒次ページ
}
private int GetData() // データを取得するユーティリティ メソッド
{
int num;
int.TryParse(defaultViewModel["NumData"] as string, out num);
return num;
}
private void SetData(int n) // データをセットするユーティリティ
{
defaultViewModel["NumData"] = n.ToString();
}
"NumData"
データバインド
【3】のサンプル コード (続き)
2014/5/10 22
UI スレッド private Task AddAsync(int n)
{
return Task.Run( // 別スレッドで非同期に実行する
async () =>
{
int num = GetData();
// await を含む長時間掛かる処理
await Task.Delay(1000);
SetData(num + n);
});
}
"NumData"
データバインド
これを実行してみる。
例えば、素早く [UP] → [DOWN] すると…
期待値: 0 ⇔ 実際: -1 …ダメぢゃん orz
データを取得して
書き戻すまでの間、
排他処理にしたい!
しょうがない、lock するか…
private Task AddAsync(int n)
{
return Task.Run(
async () =>
{
lock (this)
{
int num = GetData();
// await を含む長時間掛かる処理
await Task.Delay(1000);
SetData(num + n);
}
});
}
2014/5/10 23
えぇっ!
コンパイル エラー!?
なぜ…???
↑ 「エラー1 'await' 演算子は、lock ステートメント本体では使用できません。」
lock の元ネタ Monitor で確認
private Task AddAsync(int n)
{
return Task.Run(
async () =>
{
Monitor.Enter(this);
try
{
int num = GetData();
// await を含む長時間掛かる処理
await Task.Delay(1000);
SetData(num + n);
}
finally
{
Monitor.Exit(this);
}
});
}
2014/5/10 24
await の前後で
スレッドが変わる!
Monitor.Enter と Exit は同
じスレッドで呼び出さな
いといけない
(thread affinity)
※ UI スレッドで await した場合は、
UI スレッドで継続される
実行時エラー: System.Threading.SynchronizationLockException
await してると lock が使えない!
さぁ、どうしよう?
スレッドに結び付いてるロックの仕掛けはどれも使えないぞ
2014/5/10 25
ロックに使える主な仕掛け
thread affinity
スレッドに結び付く
• Monitor
= lock(c#) / SyncLock(VB)
• SpinLock
ごく短時間のロックならMonitorより
高パフォーマンス
• ReaderWriterLockSlim
書き込みは1スレッド限定、読み出し
は複数スレッドOK
• Mutex
プロセス間でのロックも可 (Windows
デバイス アプリを除く)
スレッドと無関係
• SemaphoreSlim
ロックの獲得と解放を別スレッド
で行ってもOK
2014/5/10 26
SemaphoreSlim で解決♪
private SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private Task AddAsync(int n)
{
return Task.Run(
async () =>
{
await _semaphore.WaitAsync();
try {
int num = GetData();
// await を含む長時間掛かる処理
await Task.Delay(1000);
SetData(num + n);
}
finally {
_semaphore.Release(); // 違うスレッドで解放してもOK
}
});
}
2014/5/10 27
もっと便利に! AsyncLock クラス
private AsyncLock _asyncLock = new AsyncLock();
private Task AddAsync(int n)
{
return Task.Run(
async () =>
{
using (await _asyncLock.LockAsync())
{
int num = GetData();
// await を含む長時間掛かる処理
await Task.Delay(1000);
SetData(num + n);
}
});
}
2014/5/10 28
AsyncLock クラスは、内部的に SemaphoreSlim を使っ
ている。using 構文で、解放忘れと無縁に!
AsyncLock クラスのソース
• Comparing two techniques
in .NET Asynchronous
Coordination Primitives
- Scott Hanselman
http://www.hanselman.com/blog/Comp
aringTwoTechniquesInNETAsynchronou
sCoordinationPrimitives.aspx
• Building Async Coordination
Primitives, Part 6: AsyncLock
- .NET Parallel Programming
- MSDN Blogs
http://blogs.msdn.com/b/pfxteam/archi
ve/2012/02/12/10266988.aspx
2014/5/10 29
まとめ
• 非同期/並列をうまく
使って、凍らない UI と
高速化を実現しよう
• ロックは敵だ!
必要最小限にとどめる設計
が肝心
• await してると lock は使
えない
⇒ SemaphoreSlim を使う
2014/5/10 30
サンプル コードとこの資料
OneDrive に置いておきます。
サンプル コードのビルドには VS 2013 Update 2 が必要 (Exp.可)
http://1drv.ms/1kKv12g
2014/5/10 31
universal Windows apps
ご清聴ありがとうございました
2014/5/10 32
http://1drv.ms/1kKv12g

Windows ストア アプリでスレッド間排他処理