Abstract
1. Is my code appropriate in line with one of the best requirements?
Not fairly appropriate. This implementation would not deal with exceptions correctly and would not cease when object is disabled or destroyed.
The proper code is both this (stops execution when disabled):
CancellationTokenSource _cancellationTokenSource;
void OnEnable() {
_cancellationTokenSource = new CancellationTokenSource();
SpawnRoutine(_cancellationTokenSource.Token);
}
async void SpawnRoutine(CancellationToken token) {
attempt {
whereas (true) {
Debug.Log("SpawnRoutine()");
await Awaitable.WaitForSecondsAsync(1, token);
}
}
catch (OperationCanceledException) { }
catch (Exception e) {
Debug.LogException(e);
}
lastly {
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
}
}
void OnDisable() => _cancellationTokenSource?.Cancel();
or this, if you need it to run when disabled:
void Begin() => SpawnRoutine();
async void SpawnRoutine() {
attempt {
whereas (true) {
Debug.Log("SpawnRoutine()");
await Awaitable.WaitForSecondsAsync(1, destroyCancellationToken);
}
}
catch (OperationCanceledException) { }
catch (Exception e) {
Debug.LogException(e);
}
}
Exception dealing with may also be moved to the extent above, e.g.
async void Begin() {
attempt {
await SpawnRoutine();
}
catch (OperationCanceledException) { }
catch (Exception e) {
Debug.LogException(e);
}
}
async Awaitable SpawnRoutine() {
whereas (true) {
Debug.Log("SpawnRoutine()");
await Awaitable.WaitForSecondsAsync(1, destroyCancellationToken);
}
}
Be aware that on this case SpawnRoutine
is async Awaitable
.
2. Can I ensure that the code would work on WebGL too, provided that WebGL doesn’t assist multithreading?
Sure.
Clarification
async void
vs async Awaitable
Not awaiting an async Awaitable
ends in hidden exceptions. Not awaiting an async void
would not cover exceptions. So, on the first look, async void
appears higher.
The issue is that this code:
void Begin() {
SpawnRoutine();
Debug.Log("Begin() finish");
}
async void SpawnRoutine() {
Debug.Log("SpawnRoutine()");
throw new InvalidOperationException("Take a look at Exception");
}
will end result within the following output:
SpawnRoutine()
Begin() finish
Take a look at Exception
In different phrases, the exception that occurred is delayed within the output.
Similar will occur when you do it like this:
void Begin() {
attempt {
SpawnRoutine();
}
catch (Exception e) {
Debug.LogException(e);
}
Debug.Log("Begin() finish");
}
async void SpawnRoutine() {
Debug.Log("SpawnRoutine()");
throw new InvalidOperationException("Take a look at Exception");
}
and even like this:
async void Begin() {
await SpawnRoutine();
}
async Awaitable SpawnRoutine() {
Debug.Log("SpawnRoutine");
throw new InvalidOperationException("Take a look at Exception");
}
void Replace() {
// To see the order of operations, as Begin is not going to finish instantly
Debug.Log("Replace()");
}
However with this code:
async void Begin() {
attempt {
await SpawnRoutine();
}
catch (Exception e) {
Debug.LogException(e);
}
}
async Awaitable SpawnRoutine() {
Debug.Log("SpawnRoutine");
throw new InvalidOperationException("Take a look at Exception");
}
void Replace() {
// To see the order of operations, as Begin is not going to finish instantly
Debug.Log("Replace()");
}
the exception will probably be as a substitute.
SpawnRoutine()
Take a look at Exception
Replace()
So, formally, one of the best observe when it comes to exception dealing with is that this.
MonoBehaviours & cancellation
Unity documentation is not totally specific about that, however async/await’s are routinely cancelled upon Play Mode exit. This ends in a rumor that it’s routinely cancelled upon object destruction which is just mistaken.
In different phrases, when you run this code
void Begin() => SpawnRoutine();
async void SpawnRoutine() {
whereas (true) {
Debug.Log("SpawnRoutine()");
await Awaitable.WaitForSecondsAsync(1);
}
}
in Unity Editor, after which delete the thing from the scene, this code will proceed to run. Similar applies to the code in construct. What’s extra necessary, it can survive switching scenes.
With a view to forestall this, you should use the cancellation token. Fortunately, Unity gives MonoBehaviour.destroyCancellationToken
, so you may simply use it:
void Begin() => SpawnRoutine();
async void SpawnRoutine() {
whereas (true) {
Debug.Log("SpawnRoutine()");
await Awaitable.WaitForSecondsAsync(1, destroyCancellationToken);
}
}
This can cancel the execution when object is destroyed, but in addition will throw an OperationCanceled exception. You may’t do a lot about it as that is how CancellationToken
was designed to behave, sadly.
So you may have do suppress the exception by dealing with it:
async void SpawnRoutine() {
attempt {
whereas (true) {
Debug.Log("SpawnRoutine()");
await Awaitable.WaitForSecondsAsync(1, destroyCancellationToken);
}
}
catch(OperationCanceledException) { }
}
(Be aware that the exception dealing with occurs throughout the void technique. If you need it to occur the extent above, you’ll need to make the strategy Awaitable.)
This can lastly cancel the async technique correctly.
One other concern with not utilizing the destroyCancellationToken
is that when you enclose your code in
attempt {
// Your code
}
lastly {
// Some cleanup
}
the code inside lastly
block executes simply positive when there may be the destroyCancellationToken
used, however would not execute in any respect when there isn’t a token.
You also needs to use destroyCancellationToken
for non-Awaitables (Activity, ValueTask, UniTask), e.g.
async Awaitable Instance() {
await File.ReadAllBytesAsync("file.txt", destroyCancellationToken);
}
in any other case studying the file is not going to be canceled both.
If you should cancel the strategy on simply disabling the script, you should create your individual cancellation token as Unity would not present one for this case.
But it surely gives Utility.exitCancellationToken
, which, in line with the documentation, is required for background duties (i.e. code working in a background thread) as in any other case the thread will proceed to run after leaving Play Mode.
WebGL & continuation
Basically, async/await is not about multithreading, it’s about asynchronous execution.
Implementations of async/await for Unity (UniTask and Awaitable) have been created with the principle thread in thoughts. It’s important to launch/change threads explicitly to have a separate one.
As well as, even the .Internet Duties must be protected to make use of in WebGL starting with the model of Unity that carried out correct synchronization context (be aware that I did not attempt that). Earlier than that, the continuation of a Activity (i.e. the code after await
) might run on a special thread, which resulted in points in WebGL.
UniTask
Unity Awaitable
is a subset of UniTask with minor variations. That is in all probability not fairly necessary for this explicit use case – changing the coroutines – however UniTask provides extra performance, WhenAny
and WhenAll
specifically, so I ought to in all probability nonetheless recommendation to make use of it as an alternative for general-purpose async programming.