async Task.Run lambda expression crashes Unity AR app on android but works fine on iOS

Viewed 296

I am creating an app with Unity 2020.3.4 and ARCore 4.0.12 and Azure Spatial Anchors 2.8.1. It is a cross platform app for Android and iOS. First I created an iOS version where I created a Task.Run(async() =>{ }); lambda expression inside a void function to be able to call Azure Spatial Anchors' async functions. I did it this way, because this function is called on a Unity button press, which does not take async Task, but it does take void's. This worked fine on iOS devices, but once I tested it on an Android device this code crashed the app.

//Called on Unity UI button press, works fine on IOS, but crashes the app on Android
public void BROKENStartLookingForAnchor()
{
    Task.Run(async () =>
    {
        if (!hasStartedOnce)
        {
            await StorageManager.instance.InitializeStorage();
            anchorId = await StorageManager.instance.DownloadBlob(blobName);
        }

        spatialAnchorManager.SessionStarted += OnSessionStarted;
        spatialAnchorManager.AnchorLocated += CloudManager_AnchorLocated;

        anchorLocateCriteria = new AnchorLocateCriteria();

        if (spatialAnchorManager.Session == null)
        {
            await spatialAnchorManager.CreateSessionAsync();
            SetAnchorLocateCriteria();
        }

        await spatialAnchorManager.StartSessionAsync();
    });
}

It was very hard to figure out the issue at first (I used LogCat to debug), but once I changed my code to the following it worked fine on Android as well.

//Called on Unity UI button press, because cannot call async Task from Unity button press
public async void StartLookingForAnchor()
{
    await StartLookingForAnchorAsync();
}

private async Task StartLookingForAnchorAsync()
{
    if (!hasStartedOnce)
    {
        await StorageManager.instance.InitializeStorage();
        anchorId = await StorageManager.instance.DownloadBlob(blobName);
    }

    spatialAnchorManager.SessionStarted += OnSessionStarted;
    spatialAnchorManager.AnchorLocated += CloudManager_AnchorLocated;

    anchorLocateCriteria = new AnchorLocateCriteria();

    if (spatialAnchorManager.Session == null)
    {
        await spatialAnchorManager.CreateSessionAsync();
        SetAnchorLocateCriteria();
    }

    await spatialAnchorManager.StartSessionAsync();
}

However, even though I fixed the issue I am still not sure why the first option does not work on Android and why the second one does because I am not very familiar with async functions and multi-threading. Can someone explain this to me?

2 Answers

Task.Run(async () => { ... }); returns a Task, which is not awaited in your first version, where BROKENStartLookingForAnchorAsync() has a return type of just void.

In your second version, the Task returned by async Task StartLookingForAnchorAsync() is awaited (which is made possible by StartLookingForAnchor() having a return type of async void).

I suspect your first version would also work if you made it return async void and made the code await Task.Run(async () => { ... });, or just removed the Task.Run() wrapping the rest of the code entirely.

Per the C# async documentation, async void return types are discouraged, except for in event handlers, which sounds like your use case with the Unity button press.

The Unity QuickStart tutorials in the Azure Spatial Anchors documentation could be a good starting point for patterns to use with Unity + ASA.

Related