One of the options is using the Dispatcher pattern. The idea of this pattern is to create an object for each thread you want to invoke on(main thread in your case) which maintains list of delegates that is being polled and executed(e.g. in Update
method). Since there is not implementation in Unity I am aware of, you either need to find Dispatcher implementation for Unity or implement it by yourself.
A very minimalist example
public class Dispatcher : MonoBehaviour
{
protected Queue<Action> _pending = new Queue<Action>();
void Update()
{
lock(_pending)
{
while(_pending.Count != 0) _pending.Dequeue().Invoke();
}
}
public void Invoke(Action a)
{
lock (_pending) {_pending.Enqueue(a);}
}
}
public class Spawn: MonoBehaviour
{
private Dispatcher _dispatcher;
void asyncSpawn()
{
for (int i = 0; i < _targetEnemyCount; i++)
{
Thread.Sleep(1000);
_dispatcher.Invoke( () => spawnEnemy());
}
}
void Start()
{
new Thread(() => asyncSpawn()).Start();
}
}