Lock in $30 Savings on PRO—Offer Ends Soon! ⏳
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
.NETの非同期戦略とUnityとの相互運用
Search
Yoshifumi Kawai
March 21, 2024
Technology
3
22k
.NETの非同期戦略とUnityとの相互運用
Game Developers Meeting Vol.61
GDM × Born Digital
Yoshifumi Kawai
March 21, 2024
Tweet
Share
More Decks by Yoshifumi Kawai
See All by Yoshifumi Kawai
CysharpのOSS群から見るModern C#の現在地
neuecc
2
5.7k
R3のコードから見る実践LINQ実装最適化・コンカレントプログラミング実例
neuecc
3
22k
他言語がメインの場合のRustの活用法 - csbindgenによるC# x Rust FFI実践事例
neuecc
7
38k
Modern High Performance C# 2023 Edition
neuecc
4
14k
CEDEC 2023 モダンハイパフォーマンスC# 2023 Edition
neuecc
8
80k
C#11 による世界最速バイナリシリアライザー「MemoryPack」の作り方
neuecc
1
40k
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
neuecc
1
320
Deep Dive async/await in Unity with UniTask(UniRx.Async)
neuecc
0
340
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
neuecc
2
970
Other Decks in Technology
See All in Technology
GeminiとUnityで実現するインタラクティブアート
hokkey621
0
640
2024年のModern Data Stackを振り返ろう~分野別の目玉アップデート情報まとめ~
sagara
0
390
Nihonbashi Test Talk #3_WebDriver BiDiと最新の実装状況 / WebDriver BiDi latest status
takeyaqa
1
150
農業用ダム監視を目的とした衛星SAR 干渉解析の適用性について
osgeojp
0
190
宇宙最速のランチRecap LT会(AWS re:Invent 2024)
watany
1
380
セキュリティ系アップデート全体像と AWS Organizations 新ポリシー「宣言型ポリシー」を紹介 / reGrowth 2024 Security
masahirokawahara
0
170
まだチケットを手動で書いてるの?!GitHub Actionsと生成AIでチケットの作成を自動化してみた話 / 20241207 Yoshinori Katayama
shift_evolve
1
790
スパイクアクセス対策としての pitchfork 導入
riseshia
0
190
店舗向けSaaSにおける 顧客要望活用の実践アプローチ(20241205_pmconf)
yujirooo
0
3.3k
ミスが許されない領域にAIを溶け込ませる プロダクトマネジメントの裏側
t01062sy
8
8.4k
A/Aテストにおけるサンプルサイズ/japanr2024
nikkei_engineer_recruiting
1
610
ソフトウェアエンジニアとしてキャリアの螺旋を駆け上がる方法 - 経験と出会いが人生を変える / Career-Anchor-Drive
soudai
13
2.9k
Featured
See All Featured
The Pragmatic Product Professional
lauravandoore
32
6.3k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
159
15k
No one is an island. Learnings from fostering a developers community.
thoeni
19
3k
The Art of Programming - Codeland 2020
erikaheidi
53
13k
Docker and Python
trallard
41
3.1k
GraphQLとの向き合い方2022年版
quramy
44
13k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
44
6.9k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
6
480
Side Projects
sachag
452
42k
Building an army of robots
kneath
302
43k
Large-scale JavaScript Application Architecture
addyosmani
510
110k
VelocityConf: Rendering Performance Case Studies
addyosmani
326
24k
Transcript
.NETの非同期戦略とUnityとの相互運用 Game Developers Meeting Vol.61 GDM x Born Digital 2024-03-21
Yoshifumi Kawai / Cysharp, Inc.
About Speaker 河合 宜文 / Kawai Yoshifumi / @neuecc Cysharp,
Inc. - CEO/CTO 株式会社Cygamesの子会社として2018年9月設立 C#関連の研究開発/OSS/コンサルティングを行う Microsoft MVP for Developer Technologies(C#) since 2011 CEDEC AWARDS 2022エンジニアリング部門優秀賞 .NETのクラスライブラリ設計 改訂新版 監訳 50以上のOSSライブラリ開発(UniRx, UniTask, MessagePack C#, etc...) C#では世界でもトップレベルのGitHub Star(合計30000+)を獲得
最近のOSS R3 - The new future of dotnet/reactive and UniRx
https://github.com/Cysharp/R3/ Unity使いの人に一言でいえば、UniRx2 UniTaskなどasync/awaitとの共存を意識して現代的に再設計 ObsevableTrackerというリーク確認ウィンドウ付きで便利 Claudia - Unofficial Anthropic Claude API client for .NET. https://github.com/Cysharp/Claudia UnityでもRuntime/Editorともに動きます! EditorでAIを活用したワークフロー改善 Runtimeで直接的なAIゲームを作ったりできます! 超レコメンデッド
Two Decades of Asynchronous Programming in C#
.NET Unity async/await Coroutine Rx UniRx UniTask Awaitable Channel ValueTask
EAP TAP APM APM = Asynchronous Programming Model(Begin/End) EAP = Event-based Asynchronous Pattern TAP = Task-based Asynchronous Pattern Rx = Reactive Extensions IAsyncEnumerable TPL Dataflow R3 C#誕生初期(2000年)から現在(2024年) までの24年間の非同期処理 TPL(Parallel) ThreadPool C# Job System
.NET Unity async/await Coroutine Rx UniRx UniTask Awaitable Channel ValueTask
EAP TAP APM IAsyncEnumerable TPL Dataflow R3 初期に構築されたものはさようなら TPL(Parallel) ThreadPool C# Job System
.NET Unity async/await Coroutine Rx UniRx UniTask Awaitable Channel ValueTask
EAP TAP APM IAsyncEnumerable TPL Dataflow R3 TPL(Parallel) ThreadPool C# Job System IValueTaskSource(.NET Standard 2.1), PoolingAsyncValueTaskMethodBuilder(.NET 6)などの強化 IThreadPoolWorkItem(.NET Core 3.0)の追加 フルManaged(C#)実装化(.NET 6)などの強化 Parallel.ForEachAsync(.NET 6) Parallel.ForAsync(.NET 8)の追加 生存してるものは折々強化さ れています!
.NET Unity async/await Coroutine Rx UniRx UniTask Awaitable Channel ValueTask
EAP TAP APM IAsyncEnumerable TPL Dataflow R3 UniTask, Awaitableは共存(むしろどち らかというとUniTaskメインに使う) 詳しくはUnityプログラミング・バイ ブル R6号に執筆したので読もう! TPL(Parallel) ThreadPool
.NET Unity async/await Coroutine Rx UniRx UniTask Awaitable Channel ValueTask
EAP TAP APM IAsyncEnumerable TPL Dataflow R3 TPL(Parallel) ThreadPool go to R3; C# Job System
.NET Unity async/await Coroutine Rx UniRx UniTask Awaitable Channel ValueTask
EAP TAP APM IAsyncEnumerable TPL Dataflow R3 TPL(Parallel) ThreadPool C# Job System Unityで使いたい場合、NuGet経由で入れる or UniTaskに簡易化したChannelがあります
NuGet in Unity
NuGetをUnityで使う 標準ライブラリが使えるシチュエーションの増加 Unityの.NET Standard 2.1準拠によって使えるライブラリが増えた Source Generatorも動作(Unity 2022.3.12f1以降ならRoslyn 4.3.0まで行ける) IL2CPP時のリフレクション・WebGL利用時のThreadPool利用など
を回避できれば、多くのライブラリが動作する 依存の解決 Managed DLL同士の依存関係を素直に解決するならNuGetに頼っ たほうがいいし、.NET x Unityで共通化していく際に実体が別物 (MessagePack-CSharpなど)だとかなり不便 .NETが近年NativeAOTの強化につき、リ フレクション利用に気を使いだしてきて、 結果としてIL2CPPでも問題なく動作する ようになってきた 最近のCysharpライブラリは NuGet化を推し進めています
NuGetをUnityで使う NuGetForUnity https://github.com/GlitchEnzo/NuGetForUnity シンプルにNuGetサーバーからDLLを落としてくるエディタ拡張 挙動が分かりやすくて良い。ただし、コードがコンパイルできな い時にはエディタ拡張も動かない→DLLが落とせない→一生コンパ イルできない、などの拡張ゆえのはまりどころもある UnityNuGet https://github.com/xoofx/UnityNuGet UnityのPackage
Managerの追加レジストリとして動作する UPMとして自然な挙動、ただしDLL配布サーバーは独自サーバー Cysharp的にはこちらを薦めています OpenUPMと合わせたりでこちらのほ うが扱いやすい、という意見もあり
Task/ValueTaskとThreadPool .NETとUnityの共通非同期インターフェイス UniTaskやAwaitableはUnity専用 .NETと共通で非同期処理を実装するならTask/ValueTaskで表す ThreadPool行きを避ける Unityの提供する非同期メソッドは完了時にエンジン側でメインス レッドに戻してくれる(AsyncOperationなど) 不要なオーバーヘッドが発生しているかもしれない うっかりThreadPool内に入るとUnityのAPIが呼べない! そしてWebGLで動作しない
UniTaskを使えば何も考えなくてもいい(最高!)けれど、 Task/ValueTaskを使わなければいけないなら……?
Continue on ThreadPool 1 async void Start() { // SynchronizationContext.SetSynchronizationContext(null);
var tcs = new TaskCompletionSource<object>(); StartCoroutine(NextFrame(tcs)); await tcs.Task; Debug.Log(Thread.CurrentThread.IsThreadPoolThread); } IEnumerator NextFrame(TaskCompletionSource<object> tcs) { yield return null; ThreadPool.QueueUserWorkItem(_ => tcs.TrySetResult(null)); } False ThreadPool上で継続を流す場合 UnityはデフォルトでUnitySynchronizationContextがセット されていて、await時にUnityのメインスレッドに戻す
Continue on ThreadPool 2 async void Start() { SynchronizationContext.SetSynchronizationContext(null); var
tcs = new TaskCompletionSource<object>(); StartCoroutine(NextFrame(tcs)); await tcs.Task; Debug.Log(Thread.CurrentThread.IsThreadPoolThread); } IEnumerator NextFrame(TaskCompletionSource<object> tcs) { yield return null; ThreadPool.QueueUserWorkItem(_ => tcs.TrySetResult(null)); } True メインスレッドに戻す機構がな いのでThreadPool上のまま UnitySynchronizationContextがセットされていない場合
ConfigureAwait : (unity-context | true) async void Start() { //
SynchronizationContext.SetSynchronizationContext(null); var tcs = new TaskCompletionSource<object>(); StartCoroutine(NextFrame(tcs)); await tcs.Task; // .ConfigureAwait(false); Debug.Log(Thread.CurrentThread.IsThreadPoolThread); } IEnumerator NextFrame(TaskCompletionSource<object> tcs) { yield return null; tcs.TrySetResult(null); } False このケースでは実は UnitySynchronizationContextは「使われ ていないの」でSynchronizationContextの パフォーマンス上のペナルティはない Unityのメインスレッド内でTaskの継続を呼ぶ
ConfigureAwait : (null-sync | true) async void Start() { SynchronizationContext.SetSynchronizationContext(null);
var tcs = new TaskCompletionSource<object>(); StartCoroutine(NextFrame(tcs)); await tcs.Task; // .ConfigureAwait(false); Debug.Log(Thread.CurrentThread.IsThreadPoolThread); } IEnumerator NextFrame(TaskCompletionSource<object> tcs) { yield return null; tcs.TrySetResult(null); } False nullをセットしてもThreadPoolにはいかない、というのも コルーチンがPlayerLoop上でTaskを継続させているので
ConfigureAwait : (unity-context | false) async void Start() { //
SynchronizationContext.SetSynchronizationContext(null); var tcs = new TaskCompletionSource<object>(); StartCoroutine(NextFrame(tcs)); await tcs.Task.ConfigureAwait(false); Debug.Log(Thread.CurrentThread.IsThreadPoolThread); } IEnumerator NextFrame(TaskCompletionSource<object> tcs) { yield return null; tcs.TrySetResult(null); } True メインスレッド上でTaskを継続させていても ConfigureAwait(false)によってThreadPool行き
ConfigureAwait : (null-sync | false) async void Start() { SynchronizationContext.SetSynchronizationContext(null);
var tcs = new TaskCompletionSource<object>(); StartCoroutine(NextFrame(tcs)); await tcs.Task.ConfigureAwait(false); Debug.Log(Thread.CurrentThread.IsThreadPoolThread); } IEnumerator NextFrame(TaskCompletionSource<object> tcs) { yield return null; tcs.TrySetResult(null); } True Synchronization Contextの有無は関係ない ConfigureAwait(false)によってThreadPoolへ行く
Task/ValueTaskのThreadPool行き条件 Task.Run ある意味、明示的なThreadPool行き JobSystemほど制限がないため、CPUを使った並列処理をカジュアル に行いたいときには便利 TaskCompletionOptions.RunContinuationAsynchronously TaskCompletionSourceのTrySetResult時に、trueで必ずThreadPool行 きするオプション。ネットワーク系ライブラリでよくある。Channel ではOptions {
AllowSynchronousContinuations } の意味が、これをど ちらに設定するかということをさす。falseにすると (RunContinuationAsynchronouslyと逆)場合によりThreadPool行きする
Task/ValueTaskのThreadPool行き条件 ConfigureAwait(false) 問答無用でThreadPoolに飛ばすの意(ThreadPool上での継続の場合 はほとんどの場合、そのままそのスレッドのまま処理する) .NETのasync/awaitのプラクティスとして「ライブラリのasyncメソッ ドはConfigureAwait(false)を使うこと」というのがあり、場合によっ て(あるいはほとんどの場合)Unity側から避ける術がない なお、ConfigureAwaitを使わない場合でも、SynchronizationContext は常に使われるわけではなく、SynchronizationContextが同一の場合、 Unityの場合でいうとメインスレッド上で継続される場合はインライ
ンで処理されていくので、UnitySynchronizationContextのオーバー ヘッドは発生しないようになっている。
Task/ValueTaskのThreadPool行き条件 ConfigureAwait(false) 問答無用でThreadPoolに飛ばすの意(ThreadPool上での継続の場合 はほとんどの場合、そのままそのスレッドのまま処理する) .NETのasync/awaitのプラクティスとして「ライブラリのasyncメソッ ドはConfigureAwait(false)を使うこと」というのがあり、場合によっ て(あるいはほとんどの場合)Unity側から避ける術がない なお、ConfigureAwaitを使わない場合でも、SynchronizationContext は常に使われるわけではなく、SynchronizationContextが同一の場合、 Unityの場合でいうとメインスレッド上で継続される場合はインライ
ンで処理されていくので、UnitySynchronizationContextのオーバー ヘッドは発生しないようになっている。 なお、UniTaskはこういった面倒ごとが絶対に発生しないよ うにConfigureAwaitというシステムそのものを排除している 非同期処理を含むNuGetライブラリをUnity WebGL対応も考 慮して作るのはめちゃくちゃ大変……、という教訓をR3の実 装で得ました(R3はきっちり全回避してWebGL対応です!) この「ライブラリはConfigureAwait(false)必須」プラクティス、現代 では不要説もある。昔、ASP.NETにSyncContextがあった&非同期対 応が不十分のためしょうがなく .Resultしたらデッドロックしまくっ た、のを回避するため生まれたプラクティスなので(現在は ASP.NETはSyncContextはない(ただしBlazorにはある)&フレームワー クが完全に非同期を考慮しているので.Resultすることもないため)
None