Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
.NETの非同期戦略とUnityとの相互運用
Search
Yoshifumi Kawai
March 21, 2024
Technology
3
37k
.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
22k
R3のコードから見る実践LINQ実装最適化・コンカレントプログラミング実例
neuecc
4
37k
他言語がメインの場合のRustの活用法 - csbindgenによるC# x Rust FFI実践事例
neuecc
7
40k
Modern High Performance C# 2023 Edition
neuecc
4
15k
CEDEC 2023 モダンハイパフォーマンスC# 2023 Edition
neuecc
8
100k
C#11 による世界最速バイナリシリアライザー「MemoryPack」の作り方
neuecc
1
41k
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
neuecc
1
410
Deep Dive async/await in Unity with UniTask(UniRx.Async)
neuecc
0
430
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
neuecc
2
1.2k
Other Decks in Technology
See All in Technology
Operating Operator
shhnjk
1
620
ゼロからはじめる採用広報
yutadayo
3
990
SREの次のキャリアの道しるべ 〜SREがマネジメントレイヤーに挑戦して、 気づいたこととTips〜
coconala_engineer
1
150
関数型プログラミングで 「脳がバグる」を乗り越える
manabeai
2
210
20250705 Headlamp: 專注可擴展性的 Kubernetes 用戶界面
pichuang
0
290
タイミーのデータモデリング事例と今後のチャレンジ
ttccddtoki
6
2.5k
ビギナーであり続ける/beginning
ikuodanaka
3
790
Delta airlines Customer®️ USA Contact Numbers: Complete 2025 Support Guide
deltahelp
0
930
オフィスビルを監視しよう:フィジカル×デジタルにまたがるSLI/SLO設計と運用の難しさ / Monitoring Office Buildings: The Challenge of Physical-Digital SLI/SLO Design & Operation
bitkey
1
120
SREのためのeBPF活用ステップアップガイド
egmc
1
200
United Airlines Customer Service– Call 1-833-341-3142 Now!
airhelp
0
170
AWS CDKの仕組み / how-aws-cdk-works
gotok365
8
300
Featured
See All Featured
Keith and Marios Guide to Fast Websites
keithpitt
411
22k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
161
15k
Agile that works and the tools we love
rasmusluckow
329
21k
Building a Modern Day E-commerce SEO Strategy
aleyda
42
7.4k
Testing 201, or: Great Expectations
jmmastey
43
7.6k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
30
2.1k
The Straight Up "How To Draw Better" Workshop
denniskardys
235
140k
Optimising Largest Contentful Paint
csswizardry
37
3.3k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
6
310
Build The Right Thing And Hit Your Dates
maggiecrowley
36
2.8k
Git: the NoSQL Database
bkeepers
PRO
430
65k
GraphQLとの向き合い方2022年版
quramy
49
14k
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