You can mix several tasks in a single threaded loop, e.g. by using idle functions (as I explained herehere).
So with a GUI event loop, when some external event happens (e.g. the user presses a button), the callback could add an idle function into the loop.
Then it is important that the idle function gives back the control to the event loop quite quickly (typically, in a few milliseconds). It could add itself again to the loop to continue some processing.
Of course, the concept of continuations is relevant, and you can see an idle-function re-adding itself as some hand-written continuation-passing style
See also this answerthis answer for more references.