One bottleneck may be the loading of the room-file, depending on how big that is. loading these in advance asynchronously and adding the room in runtime may work, another optimization is to run the room loading in "physicsprocess" and setting the thread model to multi threaded.
If you still have a hard time getting things to work you may consider the nature of "thread safety". That the tree is not thread safe means (at least in my understanding of the concept and in my many testcases) that a variable isn't guaranteed to have the same value everywhere if set from different threads one shortly after the other, but writing data from an asynchronous source (aka thread) into your tile set shouldn't be a problem as long as you don't depend on being to access that data for the next couple of frames, but the data will rather trickle in over a few milliseconds or so, which in your case I think would be absolutely fine.
alternatively, you can call the "[TileMapNode].duplicate()" method on the tilemap, which generates an exact copy of the tilemap, which should be disconnected from the scene tree (I think, if it isn't you just have to call "getparent().removechild(self)"), make your changes to that asynchronously, and when that is done remove the old one from the tree and add the updated one in it's place.
Yet another solution would be to asynchronously load the file, adding all the tiles you want to add to an array that you only load a few hundred of every frame, and thus spreading the CPU load of adding all the tiles over several ticks.