Loading.LockPersistentManager issue

I am trying to load addressable Prefabs using Addressables.LoadAssetAsync.

If I try to queue up 1 or 2 loading calls in a single frame everything seems to work okay, but if I queue up 3 or more I start seeing this LockPersistentManager in the Profiler.

I believe it’s an indication the main thread is waiting for the background loading to finish something, but I can’t figure out why it only becomes a problem when I queue up 3+ loading calls.

My Prefabs are already pretty small, and the main reason I want to queue up multiple loading calls is to try to get the background loading thread to use all of its capacity in order to make the overall loading times faster. And queuing up more loading calls is definitely faster, but it produces random long frames due to Lock persistent manager stalling the main thread.

All loading is in-game as the player moves about the world, so it’s important there are no frame stutters.

I know about the background loading priority, and it is already set to low. I also know activation of the Prefabs can cause issues, but that’s not the case here (activation occurs later).

I’ve searched and searched to try and find information on the low level specifics of the background loading thread, but there doesn’t seem to be any info anywhere. So I’m here asking if anyone has information on best practices when doing async loading of Addressables, specifically on how to avoid stalling out the main thread so it’s not just sitting there waiting on other threads.

Bumping to provide more information. The issue occurs when the background loading thread is executing something while one of the following two methods are called in the main thread (there may be more than these 2, but these are what I have identified so far).

  1. AssetBundle.LoadAssetBundle.

This method is triggered by Unity automatically a few frames after calling Addressables.LoadAssetAsync. I think it is triggered when the asset bundle that contains the asset you want to load is loaded. Unity reads the file and queues up the actual async loading of the asset via the background loading thread. However, if the background thread is currently loading some other asset or it’s dependencies, there is a chance that LockPersistentManager will stall out the main thread for a bit (it doesn’t always stall out).

My guess is Unity stalls because there’s some sort of overlap between the dependencies of the asset currently being loaded in the background, and the new asset which LoadAssetBundle was called for. I would love some confirmation of this by a Unity dev though!

  1. Application.Integrate Assets in Background.

Again, this will stall out if it’s triggered while the background loading thread is running. Again, it doesn’t always stall out, so I guess it must only stall out when there are dependency overlaps.

Is there anyone out there that has experience with this subject? All other threads I can find that mention LockPersistentManager seem to come down to some specific asset types taking a while to load (textures, audio, etc.) or some issue with activating the game objects (which is not the case here).

I would really just love some feedback on how to organize the Addressable Assets or perform the async loading calls in order to avoid LockPersistentManager as much as possible.

Thanks!

Edit: I spoke to my good friend Chat GPT and I think I have a better grasp of what’s going on.

Basically, Persistent Manager “manages resources and assets that need to remain loaded and persist across the lifecycle of the application.”

LockPersistentManager is an attempt to lock this manager, to allow one thread to do some work while avoiding race conditions with other threads.

When you see LockPersistentManager in a thread, that means the thread is waiting for another thread to finish some work. So you need to look in that other thread to see what is stalling everything! This makes sense and I feel like an idiot for not understanding this sooner. Oh well.

However, I still have a few questions. Unity is using the synchronous AssetBundle.LoadAssetBundle to load the Asset Bundle. I believe there are async versions of this method available; why doesn’t Unity use them, or some other async method that doesn’t cause the main thread to be blocked?

When loading addressable prefabs, there is no way to delay main thread integration. However, let’s say I switched to using addressable scenes, which can be activated manually. Is there some way to detect when the Persistent Manager is locked in order to delay the activation of the scene until the next frame?

Hello, I have exactly the same problem. We got few seconds freeze or lags and in profiler I see “Loading.LockPersistentManager”. Is there any solution to resolve this?

Edit: Upon further research the information I provided before was not accurate. LockPersistentManager is usually called when the background loading thread and main thread are working on loading or integrating assets at the same time, however they don’t have to be working on the same asset(s) for it to be an issue.

Either thread can lock the persistent manager at any moment, and it is possible to identify “problematic assets” in some cases by looking at the competing thread, however in many cases a thread will process a bunch of assets in sequence, which causes the other thread to stall out. In these cases there doesn’t seem to be a specific “culprit” asset, and I’m not sure how to rectify the issue other than to not have background loading occur at the same time as asset integration, which is not a great solution as it slows down the overall loading speed.

These are my observations at least.

I’ve learned some more stuff recently

Some operations on the main thread ALWAYS seem to need to interact with the Persistent Manager, and so if the background loading thread is doing work and one of these operations runs, it will be locked out until the background thread finishes. One such operation is AssetBundle.LoadAssetBundle, which is basically the final setup of the asset bundle preparing it for background loading, and is usually run two frames after initiating the load of an Asset Bundle.

Unity tries to run these operations before the background loading thread begins it’s work for the current frame, however if the background loading thread has an operation that ran over from the previous frame, that won’t be possible and you will see a LockPersistentManager stall until the background operation finishes.

It also seems like the background loading thread processes two operations at a time (probably to minimize thread switching when processing smaller asset bundles), so even after the first finishes, control of the Persistent Manager may not be relinquished until after the second operation finishes. This is particularly frustrating and it would be nice if we had some kind of control over this (forcing a thread switch after the first operation).

Because of all of this, the ideal solution would be to load smaller stuff and queue up a bunch of these operations at once, in order to fill up the background loading thread with as much work as it can handle, but still allow thread switching.

However, the async pipeline (which this is all built on) can only seem to handle 2 (or maybe 3?) operations, at least on iOS where I am running these test.

Therefore, the only solution is to load 2 asset bundles/frame, and try to get their size JUST right, so that both can be loaded in the background in a single frame (i.e., so that their processing doesn’t run over into a second frame). But you also don’t want the asset bundles to be too small, because then you are not taking full advantage of the background loading thread!

Last thing. Audio Sources seem to have some kind of issue, which I assume to be a bug, that causes the Persistent Manager to be locked during the Integrate phase.


This is problematic since if you have something being loaded in the background, it will cause a big stall on the main thread. It seems to be the only file type that has this issue, and I don’t know if there is any workaround. Otherwise there doesn’t seem to be any issues with the integration operation on the main thread and the background loading thread.