Because Reflection is being used and slow/expensive at Runtime I did some testing
** Do not access or modify the UI, game objects, or anything that runs on Unity's main thread when using Async Events. An Exception will be thrown because Unity runs on the main thread and Async Events are called on potentially multiple threads depending on your system or the end user's system
A quick overview of terms used in the testing results tables below
Game Objects = An Object in Unity Scene, or class that inherits from MonoBehaviour
Normal Sync = Normal way of subscribing to events in a Synchronous manner
publiceventAction<ILoginSession>LoggingIn;voidStart(){LoggingIn+=PlayerLoggingIn;}cprivatevoidOnApplicationQuit(){LoggingIn-=PlayerLoggingIn;}publicvoidPlayerLoggingIn(ILoginSessionloginSession){Debug.Log($"Invoking Normal Event from {nameof(PlayerLoggingIn)}");}
Dynamic Sync = Subscribe to events using Synchronous Attributes
- because methods are invoked dynamically and are not actual events / Action delegates, there is no need to unsubscribe the event
Dynamic Async = Subscribe to events using Asynchronous Attributes
- because methods are invoked dynamically and are not actual events / Action delegates, there is no need to unsubscribe the event
Dynamic Event Tests
Explanation of Test Tables Below
Blocks UI / Gameplay = If any method that is subscribed to an event does CPU intensive work (ex. for loop, foreach loop, synchronous call to FileSystem/IO, or synchronous HTTPcall(web request)) the UI / Gameplay may pause and be unresponsive for a period of time. From the tests below a user will only notice this if you have manysynchronous methods that are subscribed to Vivox Events. When possible, use Dynamic Async events or use Coroutines in Unity when using Synchronous Eventsto prevent Blocking/Unresponsiveness from the UI or Gameplay
For Loop - How many iterations of a for loop per method/event that was invoked. This is to simulate work being done after an event is fired. Example when a player joins Audio channel you loop thru some GameObjects or Instantiate them for the new player in the AudioChannel
Classes(1 event per class) = for every class on a game object there is only one method that is subscribed to a Vivox Event. Multiple classes will be on a game object to get more real-world results
Events Invoked = how many events were called for the test that was performed. Since every class only has 1 event being called/invoked then you can multiply game objects in test by the number of classes each game object has since every class only has 1 event.
Modify UI, Game Objects, call main thread = Depending on the type of event used, can I modify game objects?update the UI? (Unity UI is single threaded so async modifications to objects on the main thread will throw an exception. Also you may not receive an exception and the UI will still not update properly).
Seconds = how many seconds tocomplete/invoke all methods that have been subscribed to Vivox Events. Some tests took longer than 60 seconds as you will see below but most were more less than 60 so I kept the time interval as seconds
Game Objects
Events Type
Blocks UI / Gameplay
For Loop
Classes (1 event per class)
Events Invoked
Modify UI, Game Objects, Call main thread
Seconds
100
Normal Sync
1000
2
200
75.25 (1 min 15 seconds)
100
Dynamic Sync
1000
2
200
73.64 (1 min 13 seconds)
100
Dynamic Async
1000
2
200
33.42
100
Normal Sync
100
5
500
17.69
100
Dynamic Sync
100
5
500
21.50
100
Dynamic Async
100
5
500
8.05
Game Objects
Events Type
Block UI / Gameplay
For Loop
Classes (1 event per class)
Events Invoked
Modify UI, Game Objects, Call main thread
Seconds
100
Normal Sync
1000
1
100
37.79
100
Dynamic Sync
1000
1
100
42.44
100
Dynamic Async
1000
1
100
18.18
100
Normal Sync
100
1
100
3.834
100
Dynamic Sync
100
1
100
4.078
100
Dynamic Async
100
1
100
1.835
Game Objects
Event Type
Blocks UI / Gameplay
For Loop
Classes (1 event per class)
Events Invoked
Modify UI, Game Objects, Call main thread
Seconds
500
Normal Sync
0
5
2,500
0.824
500
Dynamic Sync
0
5
2,500
1.171
500
Dynamic Async
0
5
2,500
0.349
1000
Normal Sync
0
5
5,000
1.832
1000
Dynamic Sync
0
5
5,000
1.934
1000
Dynamic Async
0
5
5,000
0.508
Game Objects
Event Types
Blocks UI / Gameplay
For Loop
Classes (1 event per class)
Events Invoked
Modify UI, Game Objects, Call main thread
Seconds
5,000
Normal Sync
0
1
5,000
1.622
5,000
Dynamic Sync
0
1
5,000
1.891
5,000
Dynamic Async
0
1
5,000
0.716
Based on the results if you are not updating game objects, the UI, or Unity Specific Work/Functionality on the main thread then I would use Async Events. If you need to use Synchronous events, then use coroutines inside your method (If it makes sense) for better performance and to avoid blocking the main thread.
For Async games/apps where most functionality is async/multi-threaded consider using the Unity Job System(with DOTS) or combine async Task/void methods with Parallel.For / Parallel.ForEach when doing CPU intensive work. Parallel.For / Parallel.ForEach will still block the UI / main thread if not used with async void or async Task methods. Do your own research if you don't know how to use asynchronous or Parallel programming. The advice I have mentioned above is not for all use cases and also considered bad in most contexts (like async void or using async with Parellel.ForEach), especially outside of Unity Game Engine whereasync is the standard for performant applications. Keep in mind Parallel programming (as well as async) comes with a lot of gotchas that could make your code hard to debug or actually make performance worse if not used correctly. Do your own research and avoid async until you have a good understanding of how to use it in Unity