Unity3d with Firebase: Can't Start Coroutine

Viewed 411

Greeting,

I'm currently facing a problem that my coroutine can't start. This is the first time I facing this issues and I can't find a proper solution online. Much appreciated if anyone can point me to the right direction to solve this issue.

Here are the code.

path_reference.GetDownloadUrlAsync().ContinueWith((Task<Uri> task) => {

if (!task.IsFaulted && !task.IsCanceled)
            {
                Debug.Log("Download URL: " + task.Result);

                StartCoroutine(DownloadStuff(task.Result));
            }
            else
            {
                Debug.Log(task.Exception.ToString());
            }
        });
    }
IEnumerator DownloadStuff(Uri uri)
{
    Debug.Log("Start Download");

    using (var www = UnityWebRequestTexture.GetTexture(uri))
    {
        yield return www.SendWebRequest();

        if (www.isNetworkError || www.isHttpError)
        {
            Debug.Log(www.error);
        }
        else
        {
            var texture = DownloadHandlerTexture.GetContent(www);

            //Texture2D texture = new Texture2D(1, 1);

            //if you need sprite for SpriteRenderer or Image
            Sprite sprite = Sprite.Create(texture, new Rect(0.0f, 0.0f, texture.width,
                    texture.height), new Vector2(0.5f, 0.5f), 100.0f);
            Debug.Log("Finished downloading!");
        }

        Debug.Log(www.downloadProgress);
    }
}'
1 Answers

The task returned by Firebase probably finishes execution on a thread other than the main thread, and Unity coroutines can only run on the main thread.

Unity's support of multithreading and async is pretty spotty, including "eating" some errors if the continuations of those errors would execute on another thread other than the main thread.

To fix this, you need to change the function that starts your coroutine:

try {
    // Important: ConfigureAwait(true) ensures the code after this will run in the
    // same thread as the code before (which is the main thread!)
    var url = await path_reference.GetDownloadUrlAsync().ConfigureAwait(true);
    StartCoroutine(DownloadStuff(url));
} catch (Exception ex) {
    // Tip: when logging errors, use LogException and pass the whole exception,
    // that way you will get pretty links to the error line in the whole stack trace.
    Debug.LogException(ex);
}

As an aside, I usually have a few extension methods on all my projects to deal with that while staying in async-world instead of coroutine-world (because at least with async I can catch errors and not just "halt and catch fire" like Unity's coroutines)

The main ones are:

public static Task ToTask(this YieldInstruction self, MonoBehaviour owner) {
    var source = new TaskCompletionSource<object>();

    IEnumerable Routine() {
        yield return self;
        source.SetResult(null);
    }

    return source.Task;
}

private static Task SendAsync(this UnityWebRequest self, MonoBehaviour owner) {
    var source = new TaskCompletionSource<object>();

    await self.SendWebRequest().ToTask(owner);

    if (
        self.isHttpError
        || self.isNetworkError
    ) {
        source.SetException(new Exception(request.error));
        yield break;
    }

    source.SetResult(null);
}

Which you can use like this, inside a MonoBehaviour:

await new WaitForSeconds(0.2f).ToTask(this);

UnityWebRequest request = /* etc */;
await request.SendAsync(this);
var texture = DownloadHandlerTexture.GetContent(request);

Note that these methods do not require ConfigureAwait, since their SetResult/SetException invocations are ran from Unity-provided coroutine continuations.

Related