65
An other world awaits you 避避避避避避避避避避避避避 ++C++; // 岩岩 岩岩

An other world awaits you

  • Upload
    -

  • View
    26.990

  • Download
    6

Embed Size (px)

DESCRIPTION

C# 5.0/VB 11で導入された async/await と、 その背後にある実行インフラ await 演算子の展開結果 await 演算子では解決できなその他の非同期 などについての話。

Citation preview

Page 1: An other world awaits you

An other world awaits you避けては通れない非同期処理

++C++; // 岩永 信之

Page 2: An other world awaits you

An other world awaits you

別世界があなたを待っています

C# 1.0• Manage

d

C# 2.0• Generic

s

C# 3.0• LINQ

C# 4.0• Dynami

c

C# 5.0• Async

これ

非同期asynchrony

※VB 7 ~ 11 の歴史でもある

Page 3: An other world awaits you

C# 5.0• Async

An other world awaits you

別世界があなたを待っています こんな世界

フリーズしない世界 バッテリーの持ちがいい世界

“ 今までも、俺ならできたよ” 訓練された人しかできない 訓練された人でも超大変

見合ったコストでは“できない”

Page 4: An other world awaits you

アジェンダ 非同期処理の裏側 いろいろなタイプの非同期処理 Windows 8/WinRT/.NET 4.5 時代の非同期処理

Page 5: An other world awaits you

async/await前ふり

まず初めに C# 5.0/VB 11 の強力さを

Page 6: An other world awaits you

C# 5.0 非同期処理

async/await

Caller Info CallerFilePath/CallerLineNumber/

CallerMemberName 属性

細かい仕様変更 / バグ修正 foreach 変数 オーバーロード解決 名前付き引数の評価順序

最大の売り本日の主役

http://ufcpp.net/study/csharp/ap_ver5.htmlhttp://msdn.microsoft.com/en-us/library/hh678682.aspx

Page 7: An other world awaits you

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;}

同期処理の場合とほぼ同じフローで非同期処理

Page 8: An other world awaits you

await

t.Wait();

• wait for t: 類義語は stay (とどまる)• スレッドを止めて待つ

await t;

• await t: 類義語は extpect (期待する)• スレッドを止めずにコールバックを待つ

重要 : ちゃんと非同期

Page 9: An other world awaits you

複数の確認ダイアログ表示

もう少し複雑な例

確認 1チェック

確認 2チェック

確認 3チェック

No

No

Yes

Yes

Yes

確認フロー

結果表示

No

ゲームでアイテムを合成します

レア アイテムですよ?

合成強化済みですよ?

もう強化限界ですよ?

Page 10: An other world awaits you

同期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;

Page 11: An other world awaits you

非同期(旧) 画面に収まるように

フォント サイズ調整 4pt です ほんの 84 行ほど

ちなみに 部分部分を関数化して多少は

整理できます ダイアログ 3 つだからまだこ

の程度で済んでます

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 =>{

if (!result2){

onComplete(false);return;

if (this.Check3.IsChecked ?? false){

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;

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); 

Page 12: An other world awaits you

非同期( 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;

} return true; • await 演算子が増えただけ

• ダイアログが増えても平気

Page 13: An other world awaits you

さて、今日の議題

中身はどうなってるの? 非同期処理の実行インフラ await の実現方法

await で万事解決? そうでもない

並列処理 イベント型の非同期 データフロー

こんなに便利な async/await ですが…

Page 14: An other world awaits you

非同期処理の実行インフラスレッド プールとか I/O とか同期コンテキストとか

Page 15: An other world awaits you

非同期インフラ スレッド スレッド プール I/O 完了ポート UI スレッドと同期コンテキスト

Page 16: An other world awaits you

非同期インフラ スレッド スレッド プール I/O 完了ポート UI スレッドと同期コンテキスト

Page 17: An other world awaits you

スレッド 非同期処理の最も低レイヤーな部分

スレッドはプリエンプティブ※なマルチタスク ハードウェア タイマーを使って強制割り込み OS が特権的にスレッド切り替えを行う

static void Main(){ var t = new Thread(Worker2); t.Start(); Worker1();}

Worker1 Worker2

CPU をシェア定期的に切り替え

※preemptive: 先売権のある

高負荷

Page 18: An other world awaits you

スレッド 非同期処理の最も低レイヤーな部分

ただし、高負荷

細々とした大量の処理をこなすには向かない 切り替え(コンテキスト スイッチ)のコストが高すぎる

for (int i = 0; i < 1000; i++){ var t = new Thread(Worker); t.Start();}

1000 個の処理を同時実行

Page 19: An other world awaits you

スレッドを立てるコスト スレッドに紐づいたデータ

カーネル ステート : 1kB くらい ローカル スタック : 1MB くらい

イベント発生 Thread Attached/Detached イベント

Page 20: An other world awaits you

スレッド切り替えコスト カーネル モードに移行 レジスターの値を保存 lock を獲得 次に実行するスレッドの決定 lock を解放 スレッドの状態を入れ替え レジスターの値の復元 カーネル モードから復帰

※ http://blogs.msdn.com/b/larryosterman/archive/2005/01/05/347314.aspx

どれも結構時間がかかる処理

Page 21: An other world awaits you

スレッド切り替えコスト削減 切り替えの原因

一定時間経過 待機ハンドル待ち( lock 獲得とか) 同期 I/O 待ち

対策 そもそもスレッドを立てない lock-free アルゴリズム利用 I/O は非同期待ち

自前実装大変スレッドは直接使わない

スレッド プールI/O 完了ポート

Page 22: An other world awaits you

スレッド切り替えコスト削減 切り替えの原因

一定時間経過 待機ハンドル待ち( lock 獲得とか) 同期 I/O 待ち

対策 そもそもスレッドを立てない lock-free アルゴリズム利用 I/O は非同期待ち

自前実装大変スレッドは直接使わない

スレッド プールI/O 完了ポート

.NET でいうと• Thread クラスは使わない

• Task クラスを使う

.NET for Windows ストア アプリではついに削除された

.NET 3.5以前の場合は ThraedPool クラスや Timer クラス、IAsyncResult インターフェイス

Page 23: An other world awaits you

非同期インフラ スレッド スレッド プール I/O 完了ポート UI スレッドと同期コンテキスト

Page 24: An other world awaits you

2種類のマルチタスク

※cooperative

プリエンプティブ

• ハードウェア タイマーを使って強制割り込み• OS が特権的にスレッド切り替えを行う• 利点 : 公平(どんなタスクも等しく OS に制御奪われる)• 欠点 : 高負荷(切り替えコストと使用リソース量)

協調的※

• 各タスクが責任を持って終了する• 1 つのタスクが終わるまで次のタスクは始まらない• 利点 : 低負荷• 欠点 : 不公平( 1 タスクの裏切りが、全体をフリーズさせる)

Page 25: An other world awaits you

スレッド プール スレッドを可能な限り使いまわす仕組み

プリエンプティブなスレッド数本の上に 協調的なタスク キューを用意

スレッド プール

キュータスク

1タスク

2

数本のスレッドだけ用意

空いているスレッドを探して実行(長時間空かない時だけ新規スレッド作

成)

新規タスク

タスクは一度キューに溜め

Page 26: An other world awaits you

スレッド プールの性能的な工夫 Work Stealing Queue

lock-free 実装なローカル キュー できる限りスレッド切り替えが起きないように

ローカルキュー 1

ローカルキュー 2

スレッド 1 スレッド 2

グローバルキュー

①スレッドごとにキューを持つ ②

ローカル キューが空のとき、他のスレッドからタスクを奪取

Page 27: An other world awaits you

参考 : lock-free アルゴリズム

lock (_sync){    _value = SomeOperation(_value);}

long oldValue1, oldValue2;do{    oldValue1 = _value;    var newValue = SomeOperation(_value);    oldValue2 = Interlocked.CompareExchange( ref _value, newValue, oldValue1);}while (oldValue1 != oldValue2);

lock ベース

lock-free ( interlocked ベース)

OS機能に頼った競合回避(カーネル モード移行あ

り)

競合してたらやり直すCPU の Interlocked命令※を

利用競合頻度が低い時に高効率

※ アトミック性を保証した CPU命令。カーネル モード移行と比べるとだいぶ低不可interlocked: 連結した、連動した

Page 28: An other world awaits you

非同期インフラ スレッド スレッド プール I/O 完了ポート UI スレッドと同期コンテキスト

Page 29: An other world awaits you

おさらい 待機しちゃダメ

× lock

× 同期 I/O 待ち × Thread.Sleep

○ 非同期 I/O

○ タイマー( Task.Delay )

何もしていないのにスレッド立てっぱなし• スタック( 1MB )取りっぱなし• スレッド切り替えの誘発• スレッド プール上でも新しいスレッド

が立ってしまう

I/O 完了ポート

Page 30: An other world awaits you

I/O Input/Output

CPU と、 CPU の外との入出力 CPU内での計算と比べると、数ケタ遅い

ユーザー入力

通信

ストレージ

CPU

待機しちゃダメ

ハードウェア タイマー

Page 31: An other world awaits you

I/O 完了ポート※

I/O を待たない コールバックを登録 I/O 完了後、スレッド プールで続きの処理を行う

スレッド プール

タスク1

タスク2…

※ I/O completion port

あるスレッドアプリ

I/O 完了ポート( OS カーネル内)

ハードウェア

I/O開始 I/O 完了

コールバック登録

コールバック登録後、すぐにスレッド上での

処理を終了

Page 32: An other world awaits you

非同期 API を使いましょう 同じ非同期処理でも

Task.Run(() => req.GetResponse());

• スレッド内で同期 I/O• スレッド立てっぱなし

req.GetResponseAsync();

• I/O 完了ポートを使って非同期 I/O• コールバック登録後、スレッドを解放

Page 33: An other world awaits you

Sleep もダメ(例 : Sleep Sort ) 値に比例して Sleep すればソートできるんじゃね? というネタ。

new Thread(_ => { Thread.Sleep(t); q.Enqueue(x); }).Start();

Task.Delay(t).ContinueWith(_ => q.Enqueue(x));

× スレッド立てて Sleep

○タイマー利用+ コールバック

要素数分のスレッドが立つ要素数 ×1MB のスタック確

保既定の設定だと、スレッド 1,000 個くらいでOut of Memory

Page 34: An other world awaits you

ダメなものは最初から提供しない Windows 8 世代の API

WinRT ファイル操作、ネットワーク、グラフィック ランチャー、ダイアログ表示

.NET 4.5 で追加されたクラス HttpClient など

.NET for Windows ストア アプリ 同期 I/O APIや Thread クラス削除

50ミリ秒以上かかる可能性のある API は非同期 API のみ提供

Page 35: An other world awaits you

非同期インフラ スレッド スレッド プール I/O 完了ポート UI スレッドと同期コンテキスト

Page 36: An other world awaits you

シングル スレッド必須 スレッド安全なコードは高コスト

いっそ、単一スレッド動作を前提に 典型例は GUI

C#/.NET に限らずたいていの GUI フレームワークはシングル スレッド動作

低レイヤー API ( DirectX とか OpenGL とか)も、 1 つのスレッドからしか扱えない

Page 37: An other world awaits you

典型例 : UI スレッド GUI は単一スレッド動作( UI スレッド)

ユーザーからの入力受け付け 画面の更新

UI スレッドユーザー

からの入力

OK

グラフィック

更新

他のスレッド

処理中は応答不可

他のスレッドからは更新不可

Page 38: An other world awaits you

矛盾

単一スレッドからしかUI 更新でき

ないそのスレッドを止めると UI フリー

シングル スレッド推奨

マルチ スレッド推奨

OK

Page 39: An other world awaits you

解決策1. スレッド プールで重たい処理

2. UI スレッドに処理を戻してから UI 更新

UI スレッド

OK

更新

他のスレッド

重たい処理

Dispatcher.Invoke

Task.Run

※ dispatcher: 配送者

戻す役割を担うのがディスパッチャー※

Page 40: An other world awaits you

ディスパッチャーの利用例 WPF の場合※

Task.Run(() =>{    var result = HeavyWork();

    this.Dispatcher.Invoke(() =>    {        this.List.ItemsSource = result;    });});

※ WPF 、 Silverlight 、 WinRT XAML でそれぞれ書き方が少しずつ違う

• あまり意識したくないんだけども

• 自動的にはやってくれないの?

Page 41: An other world awaits you

自動化するにあたって 処理の実行場所には文脈がある

同期コンテキスト※と呼ぶ

適切な同期コンテキストを拾って実行する必要がある

文脈 要件

スレッド プール どのスレッドで実行しててもいい実行効率最優先

GUI UI の更新は、 UI スレッド上での実行が必要(ディスパッチャーを経由)

Web API どのスレッドで実行してもいいただし、どの Web リクエストに対する処理か、紐づけが必要

※ synchronization contextもちろん自作も可能

Page 42: An other world awaits you

同期コンテキストの利用例

あとは、ライブラリの中で自動的に同期コンテキストを拾ってくれれば万事解決…?

var context = SynchronizationContext.Current;

Task.Run(() =>{    var result = HeavyWork();

    context.Post(r =>    {        this.List.ItemsSource = (IEnumerable<int>)r;    }, result);});

文脈に応じた適切な実行方法をしてくれる• WPF の場合はディスパッチャーへの

Post

Page 43: An other world awaits you

完全自動化無理でした スレッド プール中で同期コンテキストを拾うと、

スレッド プールにしか返ってこない BackgroundWorker の DoWork内で、別の

BackgroundWorker を作ると破綻

必ず UI スレッドに処理を戻すと実行効率悪い ぎりぎりまでスレッド プール上で実行したい

ネイティブ⇔ .NET をまたげない WinRT XAML UI だと自動化無理

つまるところ、 JavaScript とかでそれができているのは• 単一 UI フレームワーク• 実行効率度外視

Page 44: An other world awaits you

なので、ある程度の自由を TaskSchduler の選択

Task.Run(() => HeavyWork())    .ContinueWith(t =>    {        //  スレッド プール上で実行される });

Task.Run(() => HeavyWork())    .ContinueWith(t =>    {        // UI スレッド上で実行される }, TaskScheduler.FromCurrentSynchronizationContext());

既定動作(スレッド プール)

同期コンテキストを拾う

Page 45: An other world awaits you

ちなみに、 await は 既定で同期コンテキストを拾う

await した場所で同期コンテキストを拾う (ライブラリ内でなく)利用側で なので、ネイティブ API を使っていても、ちゃんと拾え

var result = await Task.Run(() => HeavyWork());

既定動作(同期コンテキストを拾う)

挙動の変更(同期コンテキストを拾わない)var result = await Task.Run(() => HeavyWork())     .ConfigureAwait(false);

Page 46: An other world awaits you

いろいろな非同期await も万能じゃない

Page 47: An other world awaits you

await が解決するもの (1) 1往復の、 pull 型非同期処理

例 : 前述の GUI での非同期処理

UI スレッド 他のスレッド

重たい処理

await

Task.Run

var result = await Task.Run(() => HeavyWork());

Page 48: An other world awaits you

await が解決するもの (2) 1往復の、 pull 型非同期処理

非同期 I/O も好例

UI スレッド I/O 完了ポート

await

Task.RunI/O開始

I/O 完了

var result = await client.GetAsync();

Page 49: An other world awaits you

await が解決するもの (3) 1往復の、 pull 型非同期処理

ダイアログ ウィンドウの表示なんかも

UI スレッド

await

ダイアログ表示

OKユーザーがダイアログを操作

GUI フレームワーク

var result = await dialog.ShowAsync();

「 UI スレッドの入れ子」みたいなことができないので、一度処理を抜けなきゃいけない

Page 50: An other world awaits you

await が解決しないもの 並列処理 イベント型非同期 そもそも制御フローを書きにくいもの

Page 51: An other world awaits you

並列処理 マルチコア CPU を使いきりたい

Parallel クラス

Parallel LINQvar results = data.AsParallel()    .Select(x => /*  並列に実行したい処理 */);

Parallel.ForEach(data, x =>{    //  並列に実行したい処理});

Page 52: An other world awaits you

イベント型非同期処理 複数件、 push 型

センサー API

サーバーからの push 通知 ユーザー操作への応答も

ある意味では非同期処理

処理側ハンドラー登録

発生側

イベント発生

イベント発生

イベント発生

イベント発生

void Init(){    var sensor = Accelerometer.GetDefault();    sensor.Shaken += sensor_Shaken;}

void sensor_Shaken( Accelerometer sender,  AccelerometerShakenEventArgs args){    //  イベント処理}

Page 53: An other world awaits you

WinRT のイベント型非同期処理 ネイティブの向こう側で起こっていること

同期コンテキストを自動的に拾えないvoid Init(){    var sensor = Accelerometer.GetDefault();    sensor.Shaken += sensor_Shaken;}

void sensor_Shaken( Accelerometer sender,  AccelerometerShakenEventArgs args){    //  イベント処理}

UI を更新するなら明示的なディスパッチャー利用必須

Page 54: An other world awaits you

WinRT のイベント型非同期処理 Rx※ とか使うのがいいかも

var sensor = Accelerometer.GetDefault();

Observable.FromEventPattern(sensor, "Shaken")    .ObserveOn(SynchronizationContext.Current)    .Subscribe(args =>    {        //  イベント処理 });

※ Reactive Extensionshttp://msdn.microsoft.com/en-us/data/gg577609NuGet取得可・Windows ストア アプリでの利用可

TaskScheduler と同じ感覚

Page 55: An other world awaits you

制御フローを書きにくいもの await は、同期と同じように非同期を書ける

そもそも同期でも書きにくいものは苦手 2 次元的なデータフローとか ステートマシンとか

Page 56: An other world awaits you

制御フローを書きにくいもの WF とか

http://msdn.microsoft.com/en-us/vstudio/aa663328

TPL Dataflow とか http://

msdn.microsoft.com/en-us/devlabs/gg585582.aspx

Page 57: An other world awaits you

how to awaitasync/await の中身

Page 58: An other world awaits you

非同期処理 おさらい 待機しちゃダメ

コールバック 中断と再開

同期コンテキスト

task.ContinueWith( コールバック );

やるべきことはイテレーターと同じ

Page 59: An other world awaits you

イテレーター 中断と再開

class MethodEnumerator : IEnumerator<int>{    public int Current { get; private set; }    private int _state = 0;     public bool MoveNext()    {        switch (_state)        {            case 0:             Current = 1;            _state = 1;            return true;            case 1:             Current = 2;            _state = 2;            return true;            case 2:             default:            return false;        }    }}

IEnumerable<int> Method(){    yield return 1;

    yield return 2;

}

Page 60: An other world awaits you

イテレーター 中断と再開

class MethodEnumerator : IEnumerator<int>{    public int Current { get; private set; }    private int _state = 0;     public bool MoveNext()    {        switch (_state)        {            case 0:             Current = 1;            _state = 1;            return true;            case 1:             Current = 2;            _state = 2;            return true;            case 2:             default:            return false;        }    }}

IEnumerable<int> Method(){    yield return 1;

    yield return 2;

}

Current = 1;_state = 1;return true;case 1:

状態の記録

中断

再開用のラベル

Page 61: An other world awaits you

await の展開結果(コンセプト) コンセプト的には イテレーター +

ContinueWith

async Task<int> Method(){    var x = await task1;    var y = await task2;}

_state = 1;if (!task1.IsCompleted){    task1.ContinueWith(a);    return;}case 1:var x = task1.Result;

中断

状態の記録

結果の受け取り

再開用のラベル

Page 62: An other world awaits you

await の展開結果 実際はもう少し複雑

Awaiter というものを介していたり( Awaitableパターン)_state = 1;var awaiter1 = task1.GetAwaiter();if (!awaiter1.IsCompleted){    awaiter1.OnCompleted(a);    return;}case 1:var x = awaiter1.GetResult();

• こいつが同期コンテキストを拾い上げていたりする• Awaiter を自作することで、

await の挙動を変更可能• Task以外も await可能

Page 63: An other world awaits you

逆に C# 5.0 が使えなくても

イテレーターを使って await に似た非同期処理可能

C# が使えなくても IEnumerable を自作する程度の手間で await に似た

(略

そこそこめんどくさい

絶望的にめんどくさいけど、性能は出ると思う

C# 使わせてください。ほんとお願いします。

Page 64: An other world awaits you

まとめ

Page 65: An other world awaits you

まとめ スレッド( Thread )は直接使わない

スレッド プール( Task )を使う

await wait ( stay )と違って、 await ( expect )

イテレーター(中断と再開) + ContinueWith (コールバック)

同期コンテキスト

await にだって…できないことは…ある 並列処理、イベント型非同期 そもそも同期で書きにくいもの