Why UnityServices.InitializeAsync() stalls main thread?

I tried to initialize unity services as shown in docs: Initialize Unity Gaming Services | In App Purchasing | 4.2.1 (unity3d.com)
but despite the fact that it should be Async it actually stalls the main thread. I looked in the profiler and I saw it make it all in one frame - what cause a serious spike in the profiler. I know async run on the main thread in this case, but I thought UnityServices.InitializeAsync() would await sometimes and allow other game functions to run smoothly. What is my mistake?

    • actually I have the same problem with AuthenticationService.Instance.SignInAnonymouslyAsync();
      I suppose it works the similarly.

What do you mean with stalls, does the whole editor lock up as it is being busy with something? Using async/await should make it so the editor continues running.

What is the code that you use?

Are you awaiting the calls? Please post your code.

async/await doesn’t mean it’s running on a background thread. It only means the method might yield execution at some point and continue later. This can be used to move execution to a background thread but it can also be just waiting for some file or network IO. Or it might just be an API detail and the method is executed completely synchronously.

The code for UnityServices is in com.unity.services.core, so you can just look at it. Everything is executed on the main thread, the async is only there for services packages to do some async initialization. I only have the purchasing package installed and it also does its initialization synchronously.

So there’s really not much you can do. Except putting the initialization in a place where the stall isn’t too noticeable.

Thank you for your fast answear!

sing async/await should make it so the editor continues running.

That is exactly what I don’t understand. Here is my code:

public async void StartUnityServices()
{
    print("Starting UnityServices... Frame Count: " + Time.frameCount);
    var options = new InitializationOptions()
                 .SetEnvironmentName(environment);
    Task unityServicesTask = UnityServices.InitializeAsync(options);
    int i = 0;
    while (!unityServicesTask.IsCompleted)
    {
        i++;
    }
    if (unityServicesTask.IsCompleted)
    {
        print("unityServices Completed Frame Count: " +Time.frameCount + ". i=" + i);
        OnUnityServicesInitilized?.Invoke();
    }
    await unityServicesTask;
}

This is the console output:
image

And this is screenshot from the profiler:

  • As you can see UnityServices.InitializeAsync(options) run completely on the first. That is why, I think, the i integer is not being increased at all (the while(!unityServicesTask.IsCompleted)) loop run only when the task is finished).

From my thought process you need something to yield the task for in the first while loop, by e.g. using Task.Yield(). But why don’t you just use await UnityServices.InitializeAsync(options); as it is written in the documentation? Is there a specific reason for that? What you do is, I would not say wrong because I don’t know enough of the specifics of await/async and how tasks work, but it looks very strange.
Especially the await unityServicesTask;, why do you await that when it is already completed?

2 Likes

Actually, As I understand, mט code is not really different from

await UnityServices.InitializeAsync(options);

that shown in the documentation. Furthermore, I’m trying to use the ability of async in order to run peace of code while running UnityServices.InitializeAsync (not in parallel but when this function will stop in an await statement it will release control to its caller which is my “StartUnityServices” function). That is the purpose of async. If you await immediately when you call the function, you just release control to the caller of my function. Anyway, the game should not freeze.

This is wrong. As I’ve said in my previous post, async/await gives no guarantee that the method will actually free up the current thread. It’s entirely up to the async method to yield control of the current thread – it can do that immediately, only after doing some significant amount of work, or it can just block the current thread until it completes.

You cannot “use” async/await in this way as a consumer of an API. It’s entirely up to the called async method whether it will release control to its caller and UnityServices.InitializeAsync doesn’t do that.

You could switch to a background thread and then call the API but UnityServices.InitializeAsync specifically blocks that and forces you to call it from the main thread. So there’s unfortunately nothing you can do about the stall.

Thank you for the replay!
I created a small loading scene and put there the call to UnityServices.InitializeAsync() so it somehow solved the problem.

Hello,

Your problem is here:

    while (!unityServicesTask.IsCompleted)
    {
        i++;
    }

This code will manually “wait” until the task has completed synchronously. It breaks the purpose of using async in the first place, and wont work since it wont let the task actually run.
UnityWebRequests run as coroutines on the main thread, so you can’t do that. You basically need to keep a state-machine. UnityServices.Instance.State does this for you

That is the purpose of async. If you await immediately when you call the function, you just release control to the caller of my function.

This is accurate, specially because we have async void, so the caller cannot wait. Not sure why you have async void at that level though.

Anyway, the game should not freeze.

This is not accurate, just because something is async, it doesnt not mean that it will necessarily run on a different thread. It could run as a co-routine. Async makes no guarantees about threading. Only about the state machine.
This is probably what it looks like internally (somwhere down the line)

var tcs = new TaskCompletionSource<ApiResponse>();
var requestOperation = request.SendWebRequest();
var registration = cancellationToken.Register(() => Abort(request));
requestOperation.completed += _ => OnCompleted(tcs, request, registration);
return tcs.Task;

Anyway, moving on, instead, its better to spin on your update loop:

bool callbackDone = false;
public void Update() {
if (UnityServices.Instance.State == ServicesInitializationState.Uninitialized || callbackDone )
    return;
callbackDone  = true;
OnUnityServicesInitilized?.Invoke();
}

Or better yet, just use the built-in callback:

UnityServices.Instance.Initialized += ...

This is correct. Not that you need to, it effectively runs as a co-routine, using await is fine…

Here’s a simple example you should try out to better understand async:

public async void Start() { 
    await Task.Delay(3000);
    Debug.Log("Will this run before or after the first update call");
}
public async void Update() {
     Debug.Log("Update part 1 Will this happen every second, or every frame?")
     await Task.Delay (1000);
     Debug.Log("Update part 2 Will this happen every second, or every frame?")
}
1 Like

Thanks GabKBelmonte for you detailed answer.
I think:
“Update part 1” will happen every frame. That’s simple because it’s not affected from any “await” statement.

Debug.Log("Will this run before or after the first update call");

That line will run after the first update call because of the

await Task.Delay(3000); 

it will create a task that continues 3000ms and await <=> release controll to the caller function - ‘Start’ all that time. We also await on Start so we also release control to the caller of Start (which is “Unity”). So, I think it will print the message only after 3000ms while on this time the other game functions continue running. (*I think that is the main difference between Task.Delay() and UnityServices.InitializeAsync() which probably don’t await so much)

The second Debug.Log statement of the update func:

public async void Update() {
     Debug.Log("Update part 1 Will this happen every second, or every frame?")
     await Task.Delay (1000);
     Debug.Log("Update part 2 Will this happen every second, or every frame?")
}

will run every second.

I run it on unity and I got this output:


I did it the right way?

  • So is it right that there is no way to run UnityServices.InitializeAsync() without freezing the game (I think it can take something like 3000ms)? I moved it to a loading scene to hide the lag but my original post purpose was to understand this point. As far as I understand, I can’t do anything about that because it’s the function responsibility to release control with an await statement but then how many games which use UnityServices don’t have lags?

You got most right, but not totally

It will actually run every frame, just 1 second lagging behind.

Try this for simplicity:

public class CloudCall : MonoBehaviour
{
    public async void Start()
    {
        Debug.Log("Start - Start");
        await Task.Delay(3000);
        Debug.Log("Start - End");
    }

    int frameCount = 0;
    public async void Update()
    {
        if (frameCount++ > 5)
            return;
        Debug.Log($"Update part 1 | frame {frameCount}");
        await Task.Delay(1000);
        Debug.Log($"Update part 2 | frame {frameCount}");
    }
}

Moving on to the main question:

So is it right that there is no way to run UnityServices.InitializeAsync() without freezing the game

There is, just await it. The rest of the game will run fine, but anything depending on the services to execute, will need to wait until the state is initialized. I cant necessarily help there, as it is game specific, it might be very little, it might be a lot, it depends.

If you do

await UnityServices.Init();
OnUnityServicesInit();

It’ll work just fine, the callback will not be called until init is completed.

I think it can take something like 3000ms

this is a bit concerning :confused:

I can’t do anything about that because it’s the function responsibility to release control

This is the part I dont quite understand that you’re trying to achieve.

await will work just fine, the order of operations will still be respected. If you want to wait on your function StartUnityServices, you can have it return Task and just await that . That depends on the code of the rest of your game.

The frames will continue running and eventually the service will be initialized. If you wanted to measure how many frames that takes, you could do that too, but the while-loop is blocking that thread anyway.

Roughly:

int frameCount = 0;
bool initializing = false;
bool initialized = false;

public async void Update() {
    if (initialized) return;
    if (initializing) {
        Debug.Log($"It's been {frameCount++}  frames");
        return
    }
    initializing = true;
    await UnityServices.Init();
   initialized = true;
}

Update will still run every frame, and it’ll log every frame until its been initialized.