if you want to break into the GUI from your async code, that's easy. You create a global queue and use that in the GUI as just another source for events. The async code can queue whatever it wants for the GUI to act on.
If the async thread needs a result back, send a variable plus a locked semaphore across. Let the GUI clear the semaphore when the user has answered the request, wait on it in the async thread. Wrap the request/response in an object for cleaner code, debugging, exception handling, and whatnot.
That's not at all fragile. On the contrary, it's good practice to separate code and UI anyway.