unity – Is that this code utilizing Awaitable appropriate?


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.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles