Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

河合 宜文 / Kawai Yoshifumi / @neuecc New World, Inc. C# Unity

Slide 3

Slide 3 text

C#最速シリアライザ https://github.com/neuecc/MessagePack-CSharp/

Slide 4

Slide 4 text

Reactive Extensions for Unity https://github.com/neuecc/UniRx/ async/await(UniTask) async UniTask DemoAsync() { // You can await Unity's AsyncObject var asset = await Resources.LoadAsync("foo"); // .ConfigureAwait accepts progress callback await SceneManager.LoadSceneAsync("scene2").ConfigureAwai // await frame-based operation(you can also await frame c await UniTask.Delay(TimeSpan.FromSeconds(3)); // like 'yield return WaitForEndOfFrame', or Rx's Observe await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); // You can await standard task await Task.Run(() => 100); // get async webrequest async UniTask GetTextAsync(UnityWebRequest req) { var op = await req.SendWebRequest(); return op.downloadHandler.text; } var task1 = GetTextAsync(UnityWebRequest.Get("http://goog var task2 = GetTextAsync(UnityWebRequest.Get("http://bing var task3 = GetTextAsync(UnityWebRequest.Get("http://yaho // concurrent async-wait and get result easily by tuple s var (google, bing, yahoo) = await UniTask.WhenAll(task1, // You can handle timeout easily await GetTextAsync(UnityWebRequest.Get("http://unity.com"

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

using C

Slide 7

Slide 7 text

in 10 years

Slide 8

Slide 8 text

Rx vs async/await

Slide 9

Slide 9 text

Rxは非同期に適用可能なものでは? IObservable time event async IE

Slide 10

Slide 10 text

非同期を同期的に扱う仕組み static string GetSync(int page) { try { var url = "http://...?page=" + page; var html = GetHttpStringSync(url); return html; } catch { return "Error"; } } static async Task GetAsync(int page) { try { var url = "http://...?page=" + page; var html = await GetHttpStringAsync(url); return html; } catch { return "Error"; } }

Slide 11

Slide 11 text

Synchronous Asynchronous Single(1) Multiple(*) var x = f(); var x = await f(); var query = from person in sequence where person.Age >= 20 select person.Name; foreach (var item in query) { OnNext(item); } var query = from person in sequence where person.Age >= 20 select person.Name; query.Subscribe(item => { OnNext(item); }); IEnumerble IObservable T Task

Slide 12

Slide 12 text

IObservableで全てを表せること

Slide 13

Slide 13 text

複雑な制御に実は向かない

Slide 14

Slide 14 text

複雑な制御に実は向かない

Slide 15

Slide 15 text

非同期はasync/await、イベントはRx

Slide 16

Slide 16 text

What is the aysnc/await

Slide 17

Slide 17 text

async/awaitはマルチスレッド…… ではない!!!

Slide 18

Slide 18 text

非同期はマルチスレッドではない これは口を酸っぱくしてしつこく言わねばならぬ マルチスレッドになる場合もある、ぐらいが正しい コルーチンはマルチスレッドではないでしょう? JavaScriptはマルチスレッドではないでしょう? でもTaskはマルチスレッドでしょ? Yes, でもありNo, でもある Taskが元々マルチスレッドのものであり、それが流用されているた め、挙動的にもマルチスレッドになる場合も多く誤解を生みやすい

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

Absoutely No C# 5.0(.NET 4.5)でasync/awaitを実装するにあたり、 既にある構造(.NET 4.0 Task)を流用するのが早かった そもそも広く使われるまで分からないこともある 結果、負債もある midori(MicrosoftのC#風言語によるマネージドOSプロジェクト)での 非同期モデルでの考察においても、Taskに関しては特にパフォーマ ンス面で(OSみたいなシビアなものを作るには)後悔されている http://joeduffyblog.com/2015/11/19/asynchronous-everything/ 現在のC#は、それをValueTaskの独自進化で返済していってる

Slide 22

Slide 22 text

Absoutely No C# 5.0(.NET 4.5)でasync/awaitを実装するにあたり、 既にある構造(.NET 4.0 Task)を流用するのが早かった そもそも広く使われるまで分からないこともある 結果、負債もある midori(MicrosoftのC#風言語によるマネージドOSプロジェクト)での 非同期モデルでの考察においても、Taskに関しては特にパフォーマ ンス面で(OSみたいなシビアなものを作るには)後悔されている http://joeduffyblog.com/2015/11/19/asynchronous-everything/ 現在のC#は、それをValueTaskの独自進化で返済していってる

Slide 23

Slide 23 text

How work async/await

Slide 24

Slide 24 text

async Task SampleTextLoadAsync() { Debug.Log("Before LoadAsync:" +Time.frameCount); // frame:1 var textAsset = await Resources.LoadAsync("te") as TextAsset; Debug.Log("After LoadAsync:" +Time.frameCount); // frame:2 return textAsset.text; }

Slide 25

Slide 25 text

手作業 -> 自動化 callbackの連鎖を、awaitで自動的に生成して動かしてくれる 例外の伝搬や最適化などもしてくれるので、手作業でやるよりも効 率が良い場合もある async/awaitはコルーチン? 本質的な意味はCPS変換、実装の詳細はステートマシン 最適化のための実装としてコルーチンが選ばれてるだけなので、そ こを突っついてもそんな意味はない

Slide 26

Slide 26 text

非同期 is not 非同期 asyncは上位に伝搬する(asyncを呼ぶメソッドはasyncになる) 結果、asyncだけど中身が同期の場合もよくある それを毎回continuationを呼ぶ(デリゲート経由で呼び出し)するの は、デリゲートゴミ生成+呼び出しコストがあってよろしくない

Slide 27

Slide 27 text

public class MyAwaiter : INotifyCompletion { bool IsCompleted { get; } T GetResult(); void OnCompleted(Action continuation); }

Slide 28

Slide 28 text

// var result = await foo; は以下のようになる if (awaiter.IsCompleted) { // もし例外があればGetResultで再throwされる var result = awaiter.GetResult(); // ...awaitの先が実行される } else { // 継続を登録(実際は最適化されてるので毎回ラムダ式は使いません) awaiter.OnCompleted(() => { // もし例外があればGetResultで再throwされる var result = awaiter.GetResult(); // ...awaitの先が実行される }); return; }

Slide 29

Slide 29 text

全てのcontinuationを生成しない public async Task FooBarBazAsync() { await Task.Yield(); Console.WriteLine("foo"); await Task.Yield(); Console.WriteLine(“bar"); await Task.Yield(); Console.WriteLine("baz"); }

Slide 30

Slide 30 text

全てのcontinuationを生成しない public async Task FooBarBazAsync() { await Task.Yield(); Console.WriteLine("foo"); await Task.Yield(); Console.WriteLine(“bar"); await Task.Yield(); Console.WriteLine("baz"); }

Slide 31

Slide 31 text

Why UniTask

Slide 32

Slide 32 text

UniRx.Asyncの主要クラス C# 7.0からasyncの戻り値をTask以外で実装できるようになった それを利用した独自のasync対応型で構造体のTask(ValueTask相当) つまりオレオレ非同期フレームワーク C# 7.0はIncremental Compilerか、Unity 2018.3で利用可能 なぜ必要か 全てを差し替えることでTask自体の負債を完全に無視する Unity自体が特殊な実行環境なので特化することで最速を実現する

Slide 33

Slide 33 text

Unity is (概ね)シングルスレッド C++のエンジンレイヤー + C#スクリプティングレイヤー C#側での扱いはほとんどシングルスレッド (コルーチン, WWW, AsyncOperation, etc…) Taskによるasync/awaitは油断するとすぐスレッドプールに飛ばす -> Delay, ContinueWith, Run, etc… async/await(Task)にはマルチスレッド -> シングルスレッドに統合す る機能がついている(SynchronizationContext)、が、そもそもシング ルスレッドなら、その統合レイヤーは消したほうが性能も扱いやす さも上がるのではないか?

Slide 34

Slide 34 text

Unity is (概ね)シングルスレッド C++のエンジンレイヤー + C#スクリプティングレイヤー C#側での扱いはほとんどシングルスレッド (コルーチン, WWW, AsyncOperation, etc…) Taskによるasync/awaitは油断するとすぐスレッドプールに飛ばす -> Delay, ContinueWith, Run, etc… async/await(Task)にはマルチスレッド -> シングルスレッドに統合す る機能がついている(SynchronizationContext)、が、そもそもシング ルスレッドなら、その統合レイヤーは消したほうが性能も扱いやす さも上がるのではないか?

Slide 35

Slide 35 text

XxxContext is the overhead of Task ExecutionContextとSynchronizationContextの二種類のキャプチャ

Slide 36

Slide 36 text

XxxContext is the overhead of Task ExecutionContextとSynchronizationContextの二種類のキャプチャ

Slide 37

Slide 37 text

コルーチンを置き換えるためのユーティリティ UniTask.Delay UniTask.WaitUntil UniTask.WaitWhile UniTask.WaitUntilValueChanged UniTask.Run UniTask.Yield UniTask.SwitchToMainThread UniTask.SwitchToThreadPool await AsyncOperation

Slide 38

Slide 38 text

IEnumerator FooCoroutine(Func resultCallback, Func exceptionCallback) { int x = 0; Exception error = null; yield return Nanikamatu(v => x = v, ex => error = ex); if (error == null) { resultCallback(x); } else { exceptionCallback(error); } } UniTask FooAsync() { var x = await NanikasuruAsync(); return x; }

Slide 39

Slide 39 text

IEnumerator FooCoroutine(Func resultCallback, Func exceptionCallback) { int x = 0; Exception error = null; yield return Nanikamatu(v => x = v, ex => error = ex); if (error == null) { resultCallback(x); } else { exceptionCallback(error); } } UniTask FooAsync() { var x = await NanikasuruAsync(); return x; }

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

性能のためのUniTask + async/await UniTaskはUnityに特化することでTaskより遥かに性能が良い No ExecutionContext, No SynchronizationContext UniTaskはコルーチン実装よりもアロケーションが少ない 非同期部分ではUniRx(Observable)よりも性能が良い 使い勝手のためのUniTask + async/await シングルスレッド前提のためマルチスレッド的な落とし穴がない 豊富な機能を提供し、コルーチンをほぼ置き換え可能 UniTask Trackerにより、UniTaskのリークを簡単に回避可能 TaskやRxと混ぜて使うことも問題ない

Slide 42

Slide 42 text

性能のためのUniTask + async/await UniTaskはUnityに特化することでTaskより遥かに性能が良い No ExecutionContext, No SynchronizationContext UniTaskはコルーチン実装よりもアロケーションが少ない 非同期部分ではUniRx(Observable)よりも性能が良い 使い勝手のためのUniTask + async/await シングルスレッド前提のためマルチスレッド的な落とし穴がない 豊富な機能を提供し、コルーチンをほぼ置き換え可能 UniTask Trackerにより、UniTaskのリークを簡単に回避可能 TaskやRxと混ぜて使うことも問題ない

Slide 43

Slide 43 text

State of UniTask

Slide 44

Slide 44 text

public enum AwaiterStatus { /// The operation has not yet completed. Pending = 0, /// The operation completed successfully. Succeeded = 1, /// The operation completed with an error. Faulted = 2, /// The operation completed due to cancellation. Canceled = 3 } ValueTaskSourceStatusに合わせています。Taskは本当 に不要なゴミが多くて……)

Slide 45

Slide 45 text

public async UniTask FooAsync() { await UniTask.Yield(); return 10; } public enum AwaiterStatus { /// The operation has not yet completed. Pending = 0, /// The operation completed successfully. Succeeded = 1, /// The operation completed with an error. Faulted = 2, /// The operation completed due to cancellation. Canceled = 3 }

Slide 46

Slide 46 text

public async UniTask FooAsync() { await UniTask.Yield(); return 10; } public enum AwaiterStatus { /// The operation has not yet completed. Pending = 0, /// The operation completed successfully. Succeeded = 1, /// The operation completed with an error. Faulted = 2, /// The operation completed due to cancellation. Canceled = 3 }

Slide 47

Slide 47 text

public async UniTask FooAsync() { await UniTask.Yield(); return 10; } public enum AwaiterStatus { /// The operation has not yet completed. Pending = 0, /// The operation completed successfully. Succeeded = 1, /// The operation completed with an error. Faulted = 2, /// The operation completed due to cancellation. Canceled = 3 }

Slide 48

Slide 48 text

public async UniTask FooAsync() { await UniTask.Yield(); throw new System.Exception("Error"); } public enum AwaiterStatus { /// The operation has not yet completed. Pending = 0, /// The operation completed successfully. Succeeded = 1, /// The operation completed with an error. Faulted = 2, /// The operation completed due to cancellation. Canceled = 3 }

Slide 49

Slide 49 text

public async UniTask FooAsync() { await UniTask.Yield(); throw new System.Exception("Error"); } public enum AwaiterStatus { /// The operation has not yet completed. Pending = 0, /// The operation completed successfully. Succeeded = 1, /// The operation completed with an error. Faulted = 2, /// The operation completed due to cancellation. Canceled = 3 }

Slide 50

Slide 50 text

public async UniTask FooAsync() { await UniTask.Yield(); throw new OperationCanceledException(); } public enum AwaiterStatus { /// The operation has not yet completed. Pending = 0, /// The operation completed successfully. Succeeded = 1, /// The operation completed with an error. Faulted = 2, /// The operation completed due to cancellation. Canceled = 3 }

Slide 51

Slide 51 text

public async UniTask FooAsync() { await UniTask.Yield(); throw new OperationCanceledException(); } public async UniTask BarAsync() { var x = await FooAsync(); return x * 2; } public void Baz() { BarAsync().Forget(); }

Slide 52

Slide 52 text

public async UniTask FooAsync() { await UniTask.Yield(); throw new OperationCanceledException(); } public async UniTask BarAsync() { var x = await FooAsync(); return x * 2; } public void Baz() { BarAsync().Forget(); }

Slide 53

Slide 53 text

public async UniTask BarAsync() { try { var x = await FooAsync(); return x * 2; } catch (Exception ex) when (!(ex is OperationCanceledException)) { return -1; } }

Slide 54

Slide 54 text

public async UniTask BarAsync() { try { var x = await FooAsync(); return x * 2; } catch (Exception ex) when (!(ex is OperationCanceledException)) { // なんか復旧不能な例外なのでダイアログ出してタイトルに戻る的なことをするとして DialogService.ShowReturnToTitleAsync().Forget(); // fire and forget的に処理 // 元の呼びもとにはキャンセルの連鎖扱いで全てすっ飛ばして終了させる throw new OperationCanceledException(); } }

Slide 55

Slide 55 text

Cancellation of async

Slide 56

Slide 56 text

キャンセル is 面倒 Rxは戻り値のIDisposableをワンパンすれば良かったのに! (代わりにasync/awaitにはIDisposableというアロケーションはない) かわりに引数に(CancellationTokenを渡して回る) public Task FooAsync(int x, int y, CancellationToken cancellationToken = default) { var x = await BarAsync(x, y, cancellationToken); return x; }

Slide 57

Slide 57 text

キャンセル = OperationCanceledException cancellationToken.IsCancelationRequestedをユーザーコードが チェックする必要はない 何故ならユーザーコード部分は同期だから OperationCanceledExceptionを投げるのは非同期の源流 = asyncOperation.ConfigureAwait(token), UniTask.Delay(token), etc… とにかく渡す、それだけでいい 非同期の源流が処理してくれるはずなので、そこまで届ければOK まぁそれが面倒くさいんですけどね!!! (性能を落とさず)自動でやってくれる手段はどうハックしてもない

Slide 58

Slide 58 text

UnityだとMonoBehaviour/OnDestroyが便利 public class FooBehaviour : MonoBehaviour { CancellationTokenSource cts; void Start() { cts = new CancellationTokenSource(); } void OnDestroy() { cts.Cancel(); cts.Dispose(); } }

Slide 59

Slide 59 text

高コストでは? Yes! キャンセルが例外系であったり、たまにの発火なら、そこまで問題 は出ないはずですが、正常系でのキャンセルが前提になっていると、 状況によりかなり厳しいことが起こりうる 例えばシーンのMonoBehaviourに紐づけて、シーン遷移時に画面に ある10000個のCubeのキャンセルで例外が発火したら… …? UniTask.SuppressCancellationThrow UniTaskではキャンセルを(bool isCanceled, T value)に変換する SuppressCancellationThrowが用意されている ただし例外抑制ができるのは非同期の源流のみなので注意

Slide 60

Slide 60 text

Async Eventhandling

Slide 61

Slide 61 text

なんとEventをasync/awaitで実装できる await button.OnClickAsync(); await gameObject.OnCollisionEnterAsync(); というための実装がUniTaskには入ってる async UniTask TripleClick(CancellationToken token) { await button.OnClickAsync(token); await button.OnClickAsync(token); await button.OnClickAsync(token); Debug.Log("Three times clicked"); }

Slide 62

Slide 62 text

なんとEventをasync/awaitで実装できる await button.OnClickAsync(); await gameObject.OnCollisionEnterAsync(); というための実装がUniTaskには入ってる async UniTask TripleClick(CancellationToken token) { // 都度OnClick/token渡しするよりも最初にHandlerを取得するほうが高効率 using (var handler = button.GetAsyncClickEventHandler(token)) { await handler.OnClickAsync(); await handler.OnClickAsync(); await handler.OnClickAsync(); Debug.Log("Three times clicked"); }

Slide 63

Slide 63 text

まぁ、無理は少しある イベントハンドリングはRxのほうが原則的には良い コードも長くなるし性能面でも考慮すべき事項がかなり増える ただし、複雑なフローを実装する場合には、Rxのオペレーターをや りくりするよりも「同期的にイベントを待機するコード」のほうが 綺麗に素直に書ける可能性がある 手札として、こういう手法を持っておくことは悪くないでしょう。 (ReactivePropertyも同じようにawaitできます)

Slide 64

Slide 64 text

Reusable Promise

Slide 65

Slide 65 text

パフォーマンスのために ローカル変数では再利用が許されてる(一部の非同期ソースのみ) async UniTask DelayFiveAsync1() { for (int i = 0; i < 5; i++) { // 毎回Delayを生成している await UniTask.Delay(i * 1000); Debug.Log(i); } } async UniTask DelayFiveAsync2() { // Delayを再利用する var delay = UniTask.Delay(i * 1000); for (int i = 0; i < 5; i++) { await delay; Debug.Log(i); } }

Slide 66

Slide 66 text

パフォーマンスのために ローカル変数では再利用が許されてる(一部の非同期ソースのみ) async UniTask DelayFiveAsync1() { for (int i = 0; i < 5; i++) { // 毎回Delayを生成している await UniTask.Delay(i * 1000); Debug.Log(i); } } async UniTask DelayFiveAsync2() { // Delayを再利用する var delay = UniTask.Delay(i * 1000); for (int i = 0; i < 5; i++) { await delay; Debug.Log(i); } }

Slide 67

Slide 67 text

Conclusion

Slide 68

Slide 68 text

Don’t be afraid! 性能も問題ない(UniTaskを使えば) プラクティスも既に確立されている(UniTaskを使えば) やりすぎてしまう害もあまりない(強いて言えば非同期汚染) やらない理由がないレベルなので、今すぐGO Recommend to use with UniRx.Async Unityのために性能/使い勝手ともに再デザインされたasync/await Unityで使うあらゆるものがawaitできるようになる 標準のTaskを使わないことを恐れないで! あらゆる言語、そして通常の.NETも超えた最先端を行きましょう!