More Related Content Similar to An other world awaits you (20) An other world awaits you2. A n o t h e r w o r l d a w a i t s y o u
別世界があなたを待っています
C# 5.0
C# 4.0
• Async
C# 3.0 • Dynamic
• LINQ
C# 2.0 これ
• Generics
C# 1.0 asynchrony
• Managed 非同期
※VB 7~11の歴史でもある
3. A n o t h e r w o r l d a w a i t s y o u
別世界があなたを待っています
こんな世界
フリーズしない世界
バッテリーの持ちがいい世界
C# 5.0
• Async
“今までも、俺ならできたよ”
訓練された人しかできない
訓練された人でも超大変
見合ったコストでは“できない”
6. C# 5.0
非同期処理 最大の売り
async/await 本日の主役
Caller Info
CallerFilePath/CallerLineNumber/CallerMemberName属性
細かい仕様変更/バグ修正
foreach変数
オーバーロード解決
名前付き引数の評価順序
http://ufcpp.net/study/csharp/ap_ver5.html
http://msdn.microsoft.com/en-us/library/hh678682.aspx
7. C# 5.0のasync/await
Taskクラス
async修飾子
await演算子
async Task<string> GetAsync(string url)
{
var client = new HttpClient();
var res = await client.GetAsync(url);
var content = await res.Content.ReadAsStringAsync();
return content;
}
同期処理の場合と
ほぼ同じフローで非同期処理
8. await
t.Wait();
• wait for t: 類義語はstay(とどまる)
• スレッドを止めて待つ
await t;
• await t: 類義語はextpect(期待する)
• スレッドを止めずにコールバックを待つ
重要: ちゃんと非同期
9. もう少し複雑な例
複数の確認ダイアログ表示
確認フロー
ゲームで 確認 1 Yes レア アイテムですよ?
アイテムを チェック
結果表示
合成します
No
確認 2 Yes 合成強化済みですよ?
チェック
No
Yes
確認 3 もう強化限界ですよ?
チェック
No
10. 同期
if (this.Check1.IsChecked ?? false)
{
var result = Dialog.ShowDialog("確認 1", "1つ目の確認作業");
if (!result) return false;
}
if (this.Check2.IsChecked ?? false)
{
var result = Dialog.ShowDialog("確認 2", "2つ目の確認作業");
if (!result) return false;
}
if (this.Check3.IsChecked ?? false)
{
var result = Dialog.ShowDialog("確認 3", "3つ目の確認作業");
if (!result) return false;
}
return true;
11. 非同期(旧)
if (this.Check1.IsChecked ?? false)
画面に収まるように
{
Dialog.BeginShowDialog("確認 1", "1つ目の確認作業", result =>
{
if (!result)
{
onComplete(false);
フォント サイズ調整
return;
}
if (this.Check2.IsChecked ?? false)
{
Dialog.BeginShowDialog("確認 2", "2つ目の確認作業", result2 =>
{
4ptです
if (!result2)
{
onComplete(false);
return;
}
if (this.Check3.IsChecked ?? false)
{
ほんの84行ほど
Dialog.BeginShowDialog("確認 3", "3つ目の確認作業", result3 =>
{
onComplete(result3);
});
}
else
onComplete(true);
});
ちなみに
}
else if (this.Check3.IsChecked ?? false)
{
Dialog.BeginShowDialog("確認 3", "3つ目の確認作業", result3 =>
{
onComplete(result3);
});
}
部分部分を関数化して多少は
else
onComplete(true);
});
}
else if (this.Check2.IsChecked ?? false)
整理できます
{
Dialog.BeginShowDialog("確認 2", "2つ目の確認作業", result =>
{
if (!result)
{
onComplete(false);
return;
ダイアログ3つだからまだこの
}
if (this.Check3.IsChecked ?? false)
{
Dialog.BeginShowDialog("確認 3", "3つ目の確認作業", result3 =>
程度で済んでます
{
onComplete(result);
});
}
else
onComplete(true);
});
}
else if (this.Check3.IsChecked ?? false)
{
Dialog.BeginShowDialog("確認 3", "3つ目の確認作業", result3 =>
{
onComplete(result3);
});
}
else
onComplete(true);
12. 非同期(C# 5.0)
if (this.Check1.IsChecked ?? false)
{
var result = await Dialog.ShowDialogAsync("確認 1", "1つ目の確認作業");
if (!result) return false;
}
if (this.Check2.IsChecked ?? false)
{
var result = await Dialog.ShowDialogAsync("確認 2", "2つ目の確認作業");
if (!result) return false;
}
if (this.Check3.IsChecked ?? false)
{
var result = await Dialog.ShowDialogAsync("確認 3", "3つ目の確認作業");
if (!result) return false;
}
• await演算子が増えただけ
return true;
• ダイアログが増えても平気
17. スレッド
非同期処理の最も低レイヤーな部分
Worker1 Worker2
static void Main()
{
var t = new Thread(Worker2); CPUをシェア
t.Start(); 定期的に切り替え
Worker1();
}
スレッドはプリエンプティブ※なマルチタスク
ハードウェア タイマーを使って強制割り込み 高負荷
OSが特権的にスレッド切り替えを行う
※preemptive: 先売権のある
18. スレッド
非同期処理の最も低レイヤーな部分
ただし、高負荷
for (int i = 0; i < 1000; i++)
{
var t = new Thread(Worker);
t.Start();
}
1000個の処理を同時実行
細々とした大量の処理をこなすには向かない
切り替え(コンテキスト スイッチ)のコストが高すぎる
20. スレッド切り替えコスト
カーネル モードに移行
レジスターの値を保存
lockを獲得
次に実行するスレッドの決定 どれも結構時間が
lockを解放 かかる処理
スレッドの状態を入れ替え
レジスターの値の復元
カーネル モードから復帰
※ http://blogs.msdn.com/b/larryosterman/archive/2005/01/05/347314.aspx
21. スレッド切り替えコスト削減
切り替えの原因
一定時間経過
待機ハンドル待ち(lock獲得とか)
同期I/O待ち
対策
そもそもスレッドを立てない
自前実装大変
lock-freeアルゴリズム利用 スレッドは直接使わない
I/Oは非同期待ち
スレッド プール
I/O完了ポート
22. スレッド切り替えコスト削減
切り替えの原因
一定時間経過
.NETでいうと
待機ハンドル待ち(lock獲得とか)
• Threadクラスは使わない
同期I/O待ち
.NET for Windowsストア アプリではついに削除された
• Taskクラスを使う
対策 3.5以前の場合はThraedPoolクラスやTimerクラス、
.NET
IAsyncResultインターフェイス
そもそもスレッドを立てない
自前実装大変
lock-freeアルゴリズム利用 スレッドは直接使わない
I/Oは非同期待ち
スレッド プール
I/O完了ポート
24. 2種類のマルチタスク
プリエンプティブ
• ハードウェア タイマーを使って強制割り込み
• OSが特権的にスレッド切り替えを行う
• 利点: 公平(どんなタスクも等しくOSに制御奪われる)
• 欠点: 高負荷(切り替えコストと使用リソース量)
協調的※
• 各タスクが責任を持って終了する
• 1つのタスクが終わるまで次のタスクは始まらない
• 利点: 低負荷
• 欠点: 不公平(1タスクの裏切りが、全体をフリーズさせる)
※cooperative
25. スレッド プール
スレッドを可能な限り使いまわす仕組み
プリエンプティブなスレッド数本の上に
協調的なタスク キューを用意
スレッド プール
キュー
数本のスレッド
新規タスク だけ用意
タスク1
タスク2
タスクは一度
キューに溜める
…
空いているスレッドを探して実行
(長時間空かない時だけ新規スレッド作成)
26. スレッド プールの性能的な工夫
Work Stealing Queue
lock-free実装なローカル キュー
できる限りスレッド切り替えが起きないように
グローバル ローカル ローカル
キュー キュー1 キュー2
スレッド1 スレッド2
①
スレッドごとに
②
キューを持つ
ローカル キュー
が空のとき、
他のスレッドから
タスクを奪取
27. 参考: lock-freeアルゴリズム
lockベース OS機能に頼った競合回避
lock (_sync) (カーネル モード移行あり)
{
_value = SomeOperation(_value);
}
lock-free(interlockedベース)
競合してたらやり直す
long oldValue1, oldValue2; CPUのInterlocked命令※を利用
do
{
競合頻度が低い時に高効率
oldValue1 = _value;
var newValue = SomeOperation(_value);
oldValue2 = Interlocked.CompareExchange(
ref _value, newValue, oldValue1);
}
while (oldValue1 != oldValue2);
※ アトミック性を保証したCPU命令。カーネル モード移行と比べるとだいぶ低不可
interlocked: 連結した、連動した
29. おさらい
待機しちゃダメ
× lock 何もしていないのにスレッド立てっぱなし
× 同期I/O待ち • スタック(1MB)取りっぱなし
• スレッド切り替えの誘発
× Thread.Sleep • スレッド プール上でも新しいスレッド
が立ってしまう
○ 非同期I/O
I/O完了ポート
○ タイマー(Task.Delay)
30. I/O
Input/Output
CPUと、CPUの外との入出力 待機しちゃダメ
CPU内での計算と比べると、数ケタ遅い
ユーザー入力
通信
CPU
ストレージ
ハードウェア タイマー
31. I/O完了ポート※
I/Oを待たない
コールバックを登録
I/O完了後、スレッド プールで続きの処理を行う
アプリ あるスレッド スレッド プール
タスク1
コールバック登録後、 コールバック タスク2
登録
すぐにスレッド上での
…
処理を終了
I/O完了ポート
I/O開始 I/O完了
(OSカーネル内)
ハードウェア
※ I/O completion port
33. Sleepもダメ(例: Sleep Sort)
値に比例してSleepすればソートできるんじゃね?
というネタ。
×スレッド立ててSleep
new Thread(_ => { Thread.Sleep(t); q.Enqueue(x); }).Start();
要素数分のスレッドが立つ
要素数×1MBのスタック確保
既定の設定だと、スレッド1,000個くらいで
Out of Memory
○タイマー利用+コールバック
Task.Delay(t).ContinueWith(_ => q.Enqueue(x));
34. ダメなものは最初から提供しない
Windows 8世代のAPI
50ミリ秒以上かかる可能性のあるAPIは
非同期APIのみ提供
WinRT
ファイル操作、ネットワーク、グラフィック
ランチャー、ダイアログ表示
.NET 4.5で追加されたクラス
HttpClientなど
.NET for Windows ストア アプリ
同期I/O APIやThreadクラス削除
38. 矛盾
シングル スレッド推奨
単一スレッドからしか
UI更新できない
OK
そのスレッドを止める
とUIフリーズ
マルチ スレッド推奨
39. 解決策
1. スレッド プールで重たい処理
2. UIスレッドに処理を戻してからUI更新
UIスレッド 他のスレッド
Task.Run
重たい処理
Dispatcher.Invoke
更新
OK
戻す役割を担うのが
ディスパッチャー※
※ dispatcher: 配送者
40. ディスパッチャーの利用例
WPFの場合※
Task.Run(() =>
{
var result = HeavyWork();
this.Dispatcher.Invoke(() =>
{
this.List.ItemsSource = result;
});
});
• あまり意識したくないんだけども
• 自動的にはやってくれないの?
※ WPF、Silverlight、WinRT XAMLでそれぞれ書き方が少しずつ違う
41. 自動化するにあたって
処理の実行場所には文脈がある
同期コンテキスト※と呼ぶ
文脈 要件
スレッド プール どのスレッドで実行しててもいい
実行効率最優先
GUI UIの更新は、UIスレッド上での実行
が必要(ディスパッチャーを経由)
Web API どのスレッドで実行してもいい
ただし、どのWebリクエストに対す
る処理か、紐づけが必要
適切な同期コンテキストを拾って実行する必要がある
※ synchronization context
もちろん自作も可能
42. 同期コンテキストの利用例
var context = SynchronizationContext.Current;
Task.Run(() =>
{
var result = HeavyWork();
context.Post(r =>
{
this.List.ItemsSource = (IEnumerable<int>)r;
}, result);
});
文脈に応じた適切な実行方法をしてくれる
• WPFの場合はディスパッチャーへのPost
あとは、ライブラリの中で自動的に同期コンテキ
ストを拾ってくれれば万事解決…?
43. 完全自動化無理でした
スレッド プール中で同期コンテキストを拾うと、
スレッド プールにしか返ってこない
BackgroundWorkerのDoWork内で、別の
BackgroundWorkerを作ると破綻
必ずUIスレッドに処理を戻すと実行効率悪い
ぎりぎりまでスレッド プール上で実行したい
ネイティブ⇔.NETをまたげない
WinRT XAML UIだと自動化無理
つまるところ、JavaScriptとかでそれができているのは
• 単一UIフレームワーク
• 実行効率度外視
44. なので、ある程度の自由を
TaskSchdulerの選択
既定動作(スレッド プール)
Task.Run(() => HeavyWork())
.ContinueWith(t =>
{
// スレッド プール上で実行される
});
同期コンテキストを拾う
Task.Run(() => HeavyWork())
.ContinueWith(t =>
{
// UI スレッド上で実行される
}, TaskScheduler.FromCurrentSynchronizationContext());
45. ちなみに、awaitは
既定で同期コンテキストを拾う
既定動作(同期コンテキストを拾う)
var result = await Task.Run(() => HeavyWork());
挙動の変更(同期コンテキストを拾わない)
var result = await Task.Run(() => HeavyWork())
.ConfigureAwait(false);
awaitした場所で同期コンテキストを拾う
(ライブラリ内でなく)利用側で
なので、ネイティブAPIを使っていても、ちゃんと拾える
51. 並列処理
マルチコアCPUを使いきりたい
Parallelクラス
Parallel.ForEach(data, x =>
{
// 並列に実行したい処理
});
Parallel LINQ
var results = data.AsParallel()
.Select(x => /* 並列に実行したい処理 */);
52. イベント型非同期処理
複数件、push型
処理側 発生側
センサーAPI ハンドラー登録
サーバーからのpush通知
イベント発生
ユーザー操作への応答も
ある意味では非同期処理 イベント発生
イベント発生
void Init()
{
var sensor = Accelerometer.GetDefault(); イベント発生
sensor.Shaken += sensor_Shaken;
}
void sensor_Shaken(
Accelerometer sender,
AccelerometerShakenEventArgs args)
{
// イベント処理
}
53. WinRTのイベント型非同期処理
ネイティブの向こう側で起こっていること
同期コンテキストを自動的に拾えない
void Init()
{
var sensor = Accelerometer.GetDefault();
sensor.Shaken += sensor_Shaken;
}
void sensor_Shaken(
Accelerometer sender,
AccelerometerShakenEventArgs args)
{
// イベント処理 UIを更新するなら明示的な
}
ディスパッチャー利用必須
54. WinRTのイベント型非同期処理
Rx※とか使うのがいいかも
var sensor = Accelerometer.GetDefault();
Observable.FromEventPattern(sensor, "Shaken")
.ObserveOn(SynchronizationContext.Current)
.Subscribe(args =>
{
// イベント処理 TaskSchedulerと同じ感覚
});
※ Reactive Extensions
http://msdn.microsoft.com/en-us/data/gg577609
NuGet取得可・Windowsストア アプリでの利用可
56. 制御フローを書きにくいもの
WFとか
http://msdn.microsoft.com/en-us/vstudio/aa663328
TPL Dataflowとか
http://msdn.microsoft.com/en-us/devlabs/gg585582.aspx
59. イテレーター
中断と再開
class MethodEnumerator : IEnumerator<int>
{
public int Current { get; private set; }
private int _state = 0;
public bool MoveNext()
{
switch (_state)
{
case 0:
IEnumerable<int> Method()
{ Current = 1;
_state = 1;
yield return 1; return true;
case 1:
Current = 2;
yield return 2; _state = 2;
return true;
case 2:
default:
} return false;
}
}
}
60. イテレーター
中断と再開
class MethodEnumerator : IEnumerator<int>
{
public int Current { get; private set; }
private int _state = 0;
public bool MoveNext()
{
switch (_state)
{
case 0:
状態の記録
IEnumerable<int> Method() Current = 1;
{ Current = 1;
yield return 1;
_state = 1;
_state = 1;
return true;
return 1:
case true; 中断
case 1: = 2;
Current
_state = 2;
return true; 再開用のラベル
yield return 2;
case 2:
default:
} return false;
}
}
}
61. awaitの展開結果(コンセプト)
コンセプト的には イテレーター + ContinueWith
状態の記録
_state = 1;
if (!task1.IsCompleted)
async Task<int> Method() {
{ task1.ContinueWith(a);
var x = await task1;
var y = await task2; return; 中断
} }
case 1: 再開用のラベル
var x = task1.Result;
結果の受け取り
62. awaitの展開結果
実際はもう少し複雑
Awaiterというものを介していたり(Awaitableパターン)
_state = 1;
var awaiter1 = task1.GetAwaiter();
if (!awaiter1.IsCompleted)
{
awaiter1.OnCompleted(a); • こいつが同期コンテキスト
return; を拾い上げていたりする
} • Awaiterを自作することで、
case 1: awaitの挙動を変更可能
var x = awaiter1.GetResult();
• Task以外もawait可能
63. 逆に
C# 5.0が使えなくても
イテレーターを使ってawaitに似た非同期処理可能
そこそこめんどくさい
C#が使えなくても
IEnumerableを自作する程度の手間でawaitに似た(略
絶望的にめんどくさい
けど、性能は出ると思う
C#使わせてください。ほんとお願いします。
65. まとめ
スレッド(Thread)は直接使わない
スレッド プール(Task)を使う
await
wait(stay)と違って、await(expect)
イテレーター(中断と再開) + ContinueWith(コールバック)
同期コンテキスト
awaitにだって…できないことは…ある
並列処理、イベント型非同期
そもそも同期で書きにくいもの